You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- "x"
-- barContext.AO // not found
-- fooContext.AO // not found
-- globalContext.VO // found - 10
- "y"
-- barContext.AO // not found
-- fooContext.AO // found - 20
- "z"
-- barContext.AO // found - 30
varx=10;functionfoo(){vary=20;functionbarFD(){// FunctionDeclarationalert(x);alert(y);}varbarFE=function(){// FunctionExpressionalert(x);alert(y);};varbarFn=Function('alert(x); alert(y);');barFD();// 10, 20barFE();// 10, 20barFn();// 10, "y" is not defined }foo();
介绍
在变量对象中已经介绍过,执行上下文(变量,函数声明和函数形式参数)的数据被存储为变量对象的属性
此外,我们知道每次进入上下文时都会创建变量对象并填充初始值,并且它的更新发生在代码执行阶段
举个栗子🌰
这次我们讨论
作用域链 Scope Chain
定义
如果要简要说明,作用域链主要与内部函数有关
正如我们所知,ECMAScript允许创建内部函数,我们甚至可以从父函数返回这些内部函数
众所周知,每个上下文都有自己的变量对象:对于全局上下文,其变量对象就是全局对象本身,对于函数,其变量对象是活动对象
作用域链是内部上下文的所有变量对象的列表,该作用域链用于变量查找,在上面的例子中,“bar”上下文的作用域链包括AO(bar),AO(foo)和VO(global)
让我们从定义开始,进一步讨论更多的例子
函数上下文的作用域链在函数调用时创建,由该函数的活动对象和内部
[[Scope]]
属性组成用伪代码可以表示为:
根据定义,Scope可以表示为:
我们可以将Scope和[[Scope]]表示为ECMAScript数组:
我们下面将讨论AO + [[Scope]]组合以及标识符解析过程,都与函数生命周期有关
函数生命周期
函数的生命周期分为创建阶段和激活(调用)阶段
函数创建
众所周知,函数声明在进入上下文阶段时被放入变量/活动对象(VO / AO)中,让我们看一下全局上下文中的变量和函数声明(其中变量对象是全局对象本身):
在函数激活时,我们看到了正确(预期)的结果 => 30
在这里,我们看到“y”变量在函数“foo”中定义(这意味着它在“foo”上下文的AO中),但变量“x”没有在“foo”的上下文中定义,因此不会被添加到“foo”的AO
乍一看,“foo”函数根本不存在“x”变量,正如我们将在下面看到的,“foo”上下文的活动对象只包含一个属性“y”:
函数“foo”如何访问“x”变量呢?函数应该可以访问更高层上下文的变量对象,实际上,确实如此,这个机制是通过函数的内部[[Scope]]属性来实现的
注意一点,[[Scope]]与Scope(作用域链)是不同的,前者是函数的属性,后者是上下文的属性
以上述例子为例,“foo”函数的[[Scope]]如下所示:
之后,函数调用时,会进入一个函数上下文,其中活动对象被创建,并且
this
值和Scope(作用域链)被确定函数激活
正如定义中提到的那样,在进入上下文并且在创建AO / VO之后,上下文的Scope属性(作用域链,用于变量查找)定义为:
这里要强调活动对象是Scope数组的第一个元素,即添加到作用域链的最前面:
这个特征对标识符解析过程非常重要
标识符解析是确定变量(或函数声明)属于作用域链中哪个变量对象的过程
这个算法返回的是一个Reference类型的值,其base属性是相应的变量对象(如果没有找到变量,则为null),其property name属性的名字是查找到的标识符的名称,细节可参考 this
标识符解析的过程包括与变量名称对应的属性查找,即从作用域链的最底层上下文一直到最上层上下文
因此,查找过程中上下文的局部变量比父上下文的变量具有更高的优先级,如果两个相同名字的变量存在于不同的上下文中时,处于底层上下文的变量会优先被找到
让我们看一个稍微复杂的例子:
上述代码,对应了如下的变量/活动对象,函数的[[Scope]]属性以及上下文的作用域链:
全局上下文的变量对象是:
在创建foo时,foo的[[Scope]]属性为:
在foo函数调用中,foo函数上下文的活动对象是:
foo函数上下文的作用域链是:
在创建内部“bar”函数时[[Scope]]属性是:
在bar函数调用中,bar函数上下文的活动对象是:
“bar”函数上下文的作用域链是:
“x”,“y”和“z”标识符的查找过程:
作用域的特性
让我们考虑一些与作用域链和函数[[Scope]]属性相关的重要特性
闭包
ECMAScript中的闭包与函数的[[Scope]]属性直接相关,正如前面指出的那样,[[Scope]]在创建函数时保存并存在,直到函数对象被销毁。实际上,闭包恰好是函数代码和其[[Scope]]属性的组合,因此,[[Scope]]包含了函数创建所在的词法环境(父变量对象),上层上下文中的变量,可以在函数激活的时候,通过变量对象的词法链(函数创建时保存)查找到
例子:
我们看到x变量在foo函数的[[Scope]中被找到,也就是说,变量的查找是在函数创建时定义的词法(闭包)链,而不是调用的动态链(否则x变量将被解析为20)
闭包的另一个经典例子:
我们再次看到,对于标识符解析,使用函数创建时定义的词法作用域链,变量x被解析为10,而不是30
此外,这个例子清楚地表明函数的[[Scope]]属性,即使在函数上下文已经结束,也会继续存在
通过Function构造器创建的函数的[[Scope]]属性
在上面的例子中,我们看到函数创建时就获得[[Scope]]属性,并通过此属性访问所有父上下文的变量,但这有一个重要的例外,就是通过Function构造器创建的函数
正如我们所看到的,对于通过Function构造器创建的barFn函数,变量y不可访问,但它并不意味着barFn函数没有内部的[[Scope]]属性(否则它将无法访问变量x)
问题是通过Function构造器创建的函数的[[Scope]]属性始终只包含全局对象
二维作用域链查找
在作用域链查找中的一个重点是变量对象的原型,因为ECMAScript的原型特性:
如果在对象中没有直接找到属性,则查找会在原型链中进行
如果在Object.prototype中定义属性,我们可以观察到这种效果:
活动对象没有原型,我们可以在下面的例子中看到:
如果bar函数上下文的活动对象有一个原型,那么属性x应该在Object.prototype中找到,因为它不存在于AO中
但是在上面的第一个例子中,遍历标识符查找中的作用域链,我们到达全局对象,该对象从Object.prototype继承,因此x被解析为10
全局和eval上下文的作用域链
全局上下文的作用域链中只包含全局对象
“eval”代码的上下文和调用上下文(calling context)有相同的作用域链
引用
ECMA-262-3 in detail. Chapter 4. Scope chain.
The text was updated successfully, but these errors were encountered: