La portée est l'un des concepts les plus importants de JavaScript. Si vous souhaitez bien apprendre JavaScript, vous devez comprendre les principes de fonctionnement de la portée et de la chaîne de portée de JavaScript. L'article d'aujourd'hui fournit une brève introduction à la portée et à la chaîne de portée de JavaScript, dans l'espoir d'aider tout le monde à mieux apprendre JavaScript.
1. Portée JavaScript
Tout langage de programmation a le concept de portée. En termes simples, la portée est la plage accessible de variables et de fonctions, c'est-à-dire que la portée contrôle la visibilité et le cycle de vie des variables et des fonctions. En JavaScript, il existe deux types de portée de variable : la portée globale et la portée locale.
1. Portée mondiale
Les objets accessibles n'importe où dans le code ont une portée globale. De manière générale, les situations suivantes ont une portée globale :
(1) La fonction la plus externe et les variables définies en dehors de la fonction la plus externe ont une portée globale, par exemple :
var authorName="小平果"; function doSomething(){ var blogName="你不知道的JavaScript"; function innerSay(){ alert(blogName); } innerSay(); } alert(authorName); //小平果 alert(blogName); //脚本错误 doSomething(); //你不知道的JavaScript innerSay() //脚本错误
(2) Toutes les variables non définies et directement affectées sont automatiquement déclarées comme ayant une portée globale, par exemple :
function doSomething(){ var authorName="小平果"; blogName="你不知道的JavaScript"; alert(authorName); } doSomething(); //小平果 alert(blogName); //你不知道的JavaScript alert(authorName); //脚本错误
La variable blogName a une portée globale, tandis que authorName n'est pas accessible en dehors de la fonction.
(3) Toutes les propriétés des objets fenêtre ont une portée globale
Généralement, les propriétés intégrées de l'objet window ont une portée globale, telle que window.name, window.location, window.top, etc.
2. Portée locale
Contrairement à la portée globale, la portée locale n'est généralement accessible que dans un fragment fixe de code, le plus souvent au sein d'une fonction. Ainsi, à certains endroits, vous verrez également des gens faire référence à cette portée comme une portée de fonction, comme ce qui suit. et la fonction innerSay dans le code n'a qu'une portée locale.
function doSomething(){ var blogName="你不知道的JavaScript"; function innerSay(){ alert(blogName); } innerSay(); } alert(blogName); //脚本错误 innerSay(); //脚本错误
2. Chaîne de portée
En JavaScript, les fonctions sont aussi des objets. En fait, tout en JavaScript est un objet. Les objets fonction, comme les autres objets, possèdent des propriétés accessibles via le code et un ensemble de propriétés internes accessibles uniquement au moteur JavaScript. L'une des propriétés internes est [[Scope]], définie par la troisième édition de la norme ECMA-262. Cette propriété interne contient la collection d'objets dans la portée dans laquelle la fonction est créée. Cette collection est appelée la chaîne de portée de la fonction. , qui détermine quelles données sont accessibles par la fonction.
Lorsqu'une fonction est créée, sa chaîne de portée est remplie d'objets de données accessibles dans la portée dans laquelle la fonction a été créée. Par exemple, définissez la fonction suivante :
function add(num1,num2) { var sum = num1 + num2; return sum; }
Lorsque la fonction add est créée, sa chaîne de portée sera remplie avec un objet global, qui contient toutes les variables globales, comme le montre la figure ci-dessous (remarque : l'image n'illustre qu'une partie de toutes les variables) :
La portée de la fonction add sera utilisée lors de l'exécution. Par exemple, exécutez le code suivant :
var total = add(5,10);
执行此函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。
这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:
在函数执行过程中,没遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。
三、作用域链和代码优化
从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。如上图所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。例如下面的代码:
function changeColor(){ document.getElementById("btnChange").onclick=function(){ document.getElementById("targetCanvas").style.backgroundColor="red"; }; }
这个函数引用了两次全局变量document,查找该变量必须遍历整个作用域链,直到最后在全局对象中才能找到。这段代码可以重写如下:
function changeColor(){ var doc=document; doc.getElementById("btnChange").onclick=function(){ doc.getElementById("targetCanvas").style.backgroundColor="red"; }; }
这段代码比较简单,重写后不会显示出巨大的性能提升,但是如果程序中有大量的全局变量被从反复访问,那么重写后的代码性能会有显著改善。
四、改变作用域链
函数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。
with语句是对象的快捷应用方式,用来避免书写重复代码。例如:
function initUI(){ with(document){ var bd=body, links=getElementsByTagName("a"), i=0, len=links.length; while(i < len){ update(links[i++]); } getElementById("btnInit").onclick=function(){ doSomething(); }; } }
这里使用width语句来避免多次书写document,看上去更高效,实际上产生了性能问题。
当代码运行到with语句时,运行期上下文的作用域链临时被改变了。一个新的可变对象被创建,它包含了参数指定的对象的所有属性。这个对象将被推入作用域链的头部,这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问代价更高了。如下图所示:
因此在程序中应避免使用with语句,在这个例子中,只要简单的把document存储在一个局部变量中就可以提升性能。
另外一个会改变作用域链的是try-catch语句中的catch语句。当try代码块中发生错误时,执行过程会跳转到catch语句,然后把异常对象推入一个可变对象并置于作用域的头部。在catch代码块内部,函数的所有局部变量将会被放在第二个作用域链对象中。示例代码:
try{ doSomething(); }catch(ex){ alert(ex.message); //作用域链在此处改变 }
请注意,一旦catch语句执行完毕,作用域链机会返回到之前的状态。try-catch语句在代码调试和异常处理中非常有用,因此不建议完全避免。你可以通过优化代码来减少catch语句对性能的影响。一个很好的模式是将错误委托给一个函数处理,例如:
try{ doSomething(); }catch(ex){ handleError(ex); //委托给处理器方法 }
Dans le code optimisé, la méthode handleError est le seul code exécuté dans la clause catch. Cette fonction reçoit un objet d'exception en tant que paramètre, afin que vous puissiez gérer les erreurs de manière plus flexible et uniforme. Étant donné qu'une seule instruction est exécutée et qu'aucune variable locale n'est accessible, les modifications temporaires dans la chaîne de portée n'affecteront pas les performances du code.
Ce qui précède représente l'intégralité du contenu de cet article. J'espère qu'à travers cet article, tout le monde comprendra mieux la portée et la chaîne de portée de JavaScript et que nous pourrons progresser ensemble.