对于这个我用蹩脚的英语翻译了一篇英文文章,便于之后查阅,原本这文章是有中文版的可是如今连接跳不过去QAQ。express
原文连接:http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/数组
这篇文章不要求所有看懂,好比with不必去纠结,由于如今with和eval是不推荐使用的,可是看完这篇文章应该要对做用域和做用域链有个概念,这将会帮助理解js的this这些。闭包
本人英语水平垃圾和知识水平有限 ,文章是根据个人理解来翻译的,若是有翻译不恰当的地方,请务必在评论中纠正,谢谢♪(・ω・)ノ。app
一、Introduction 简介less
二、Definition 定义ecmascript
2.1 Function life cycle 函数生命周期ide
2.1.1 Function creation 函数的建立函数
2.1.2 Function activation 函数的激活ui
2.2 Scope features 做用域的面纱this
2.2.1 Closures 闭包
2.2.2 [[Scope]] of functions created via Function constructor 经过构造函数的函数建立的[[Scope]]
2.2.3 Two-dimensional Scope chain lookup 二维做用域链查找
2.2.4 Scope chain of the global and eval contexts 全局做用域链和eval执行环境
2.2.5 Affecting on Scope chain during code execution 代码执行过程当中对做用域链的影响
As we already know from the second chapter concerning the variable object, the data of an execution context (variables, function declarations, and formal parameters of functions) are stored as properties of the variables object.
正如咱们已经从第二章关于变量对象所知道的,执行环境的数据(变量、函数声明和函数的形式参数)被存储为变量对象的属性。
Also, we know that the variable object is created and filled with initial values every time on entering the context, and that its updating occurs at code execution phase.
并且,咱们知道可变对象在每次进入执行环境时被建立并初始化,而且它的更新发生在代码执行阶段。
This chapter is devoted one more detail directly related with execution contexts; this time, we will mention a topic of a scope chain.
本章将详细介绍与执行环境直接相关的内容;此次,咱们将提到做用域链的主题。
二、定义
If to describe briefly and showing the main point, a scope chain is mostly related with inner functions.
若是要简要描述和显示要点,做用域链多与function有关。
As we know, ECMAScript allows creation of inner functions and we can even return these functions from parent functions.
正如咱们所知,ECMAScript容许建立内部函数,甚至能够从父函数返回这些函数。
var x = 10; function foo() { var y = 20; function bar() { alert(x + y); } return bar; } foo()(); // 30
Thus, is known that every context has its own variables object: for the global context it is global object itself, for functions it is the activation object.
所以,咱们知道每一个执行环境都拥有变量对象。对于全局他的变量对象就是它本身,对于函数来讲,它是个活跃对象。
And the scope chain is exactly this list of all (parent) variable objects for the inner contexts. This chain is used for variables lookup. I.e. in the example above, scope chain of “bar” context includes AO(bar), AO(foo) and VO(global).
做用域链正好是该函数执行环境的全部(父)变量对象的列表。这个做用域链用于变量查找。也就是说,在上面的例子中,“bar”上下文的范围链包括AO(bar)、AO(Foo)和VO(全局)。
But, let’s examine this topic in detail.
可是,让咱们详细研究这个话题。
Let’s begin with the definition and further will discuss deeper on examples.
让咱们从定义开始,进一步讨论例子。
Scope chain is related with an execution context a chain of variable objects which is used for variables lookup at identifier resolution.
做用域链与执行环境相关联的变量对象,用于变量的标识符查找解析。
The scope chain of a function context is created at function call and consists of the activation object and the internal [[Scope]] property of this function. We will discuss the [[Scope]] property of a function in detail below.
函数做用域链在函数调用中建立,并由该函数的活跃对象和内部[[Scope]]属性组成。下面咱们将详细讨论函数的[[Scope]]属性。
Schematically in the context:
如上示意:
activeExecutionContext = { VO: {...}, // or AO → VO就是variable object 变量对象 AO就是 activation Object 活跃对象
this: thisValue, Scope: [ // Scope chain 做用域链 // list of all variable objects 全部变量对象列表 // for identifiers(标识符) lookup 用于标识符查找 ] };
where Scope by definition is:
在那里做用域的定义:
Scope = AO + [[Scope]]
For our examples we can represent Scope, and [[Scope]] as normal ECMAScript arrays:
咱们的例子中可使用正常的js数组来表明做用域链:
var Scope = [VO1, VO2, ..., VOn]; // scope chain 做用域链
The alternative structure view can be represented as a hierarchical object chain with the reference to the parent scope (to the parent variable object) on every link of the chain. For this view corresponds __parent__ concept of some implementations which we discussed in the second chapter devoted variable object:
另外一个结构视图能够表示为链表的每一个链上的父对象范围(父变量对象)的分层对象链。对于这一观点,咱们在第二章中讨论了一些变量对象:
var VO1 = {__parent__: null, ... other data}; --> var VO2 = {__parent__: VO1, ... other data}; --> // etc.
But to represent a scope chain using an array is more convenient, so we will use this approach. Besides, the specification statements abstractly itself (see 10.1.4) that “a scope chain is a list of objects”, regardless that on the implementation level can be used the approach with the hierarchical chain involving the __parent__ feature. And the array abstract representation is a good candidate for the list concept.
可是,使用数组表示做用域链更方便,因此咱们将使用这种方法。此外,规范声明自己抽象(参见101.4),“做用域链是对象列表”,不管那种实现方式,均可以使用__parent__为特征的做用域链。数组表示抽象列表概念是一个很好的选择。
The combination AO + [[Scope]] and also process of identifier resolution, which we will discuss below, are related with the life cycle of functions.
AO + [[Scope]] 的标识符解析。咱们将在下面函数的生命周期讨论。
2.一、函数生命周期
Function life cycle is divided into a stage of creation and a stage of activation (call). Let’s consider them in detail.
函数的生命周期分为,建立阶段、激活阶段(运行)。让咱们自仔细考虑一下。
2.1.一、函数的建立
As is known, function declarations are put into variable/activation object (VO/AO) on entering the context stage. Let’s see on the example a variable and a function declaration in the global context (where variable object is the global object itself, we remember, yes?):
咱们都知道,执行环境阶段,函数的声明被放入变量对象或活跃对象中。让咱们在全局环境(全局环境的变量对象是全局对象自己,咱们记得,是吗?),看看这个例子的变量和函数声明。
var x = 10; function foo() { var y = 20; alert(x + y); } foo(); // 30
At function activation, we see correct (and expected) result – 30. However, there is one very important feature.
运行函数后,咱们看见 alert弹出的是30。然而这里有个很重要的特征。
Before this moment we spoke only about variable object of the current context. Here we see that “y” variable is defined in function “foo” (which means it is in the AO of “foo” context), but variable “x” is not defined in context of “foo” and accordingly is not added into the AO of “foo”. At first glance “x” variable does not exist at all for function “foo”; but as we will see below — only “at first glance”. We see that the activation object of “foo” context contains only one property — property “y”:
在此以前,咱们只讨论了执行环境的变量对象。这里咱们看到变量y在函数foo的定义(也就是y在foo的执行环境的活跃对象【AO】里)。可是变量x不在foo中定义,所以x不会被添加到函数foo的活跃对象AO里。乍一看,变量x不存在于函数foo。可是正如咱们看到的,就是"乍一看"。咱们看到foo的执行环境的活跃对象AO只包含了一个属性y:
fooContext.AO = { y: undefined // undefined – on entering the context, 20 – at activation };
How does function “foo” have access to “x” variable? It is logical to assume that function should have access to the variable object of a higher context. In effect, it is exactly so and, physically this mechanism is implemented via the internal [[Scope]] property of a function.
函数“foo”如何访问“x”变量?假定函数应该访问较高执行环境的变量对象是复合逻辑的。实际上,它是这样的,在物理上,这个机制是经过函数的[[Scope]]属性来实现的。
[[Scope]] is a hierarchical chain of all parent variable objects, which are above the current function context; the chain is saved to the function at its creation.
在当前函数执行环境[[Scope]]是父对象的链,链在被建立的时候保存到函数中.
Notice the important point — [[Scope]] is saved at function creation — statically (invariably), once and forever — until function destruction. I.e. function can be never called, but [[Scope]] property is already written and stored in function object.
注意重要的一点,[[Scope]]在函数建立时永远静止不变地被保存,直到函数销毁。I.e. 函数不被调用,可是[[Scope]]属性已经写好存储在函数对象中。
Another moment which should be considered is that [[Scope]] in contrast with Scope (Scope chain) is the property of a function instead of a context. Considering the above example, [[Scope]] of the “foo” function is the following:
此时值得考虑的是做用域[[Scope]]和做用域链的对比而不是执行环境的对比。就上述而论,foo的[[Scope]]以下:
foo.[[Scope]] = [ globalContext.VO // === Global ===全局 ];
And further, by a function call as we know, there is an entering a function context where the activation object is created and this value and Scope (Scope chain) are determined. Let us consider this moment in detail.
此外,经过咱们知道的函数调用。函数执行环境建立其活跃对象建立它的值,肯定做用域链。让咱们详细地考虑一下这个时候。
2.1.二、函数激活
As it has been said in definition, on entering the context and after creation of AO/VO, Scope property of the context (which is a scope chain for variables lookup) is defined as follows:
正如在定义中所说的,推入执行环境和建立AO或者是VO,做用域的属性在执行环境中(它是变量查找的做用域链),定义以下:
Scope = AO|VO + [[Scope]]
High light here is that the activation object is the first element of the Scope array, i.e. added to the front of scope chain:
这里的高亮是活跃对象(//也就是说的[AO])在做用域数组的第一个元素。i.e.添加到做用域链的前面:
Scope = [AO].concat([[Scope]]);
This feature is very important for the process of identifier resolution.
这个特性对于标识符的解析过程很是重要。
Identifier resolution is a process of determination to which variable object in scope chain the variable (or the function declaration) belongs.
标识符的解析是一个肯定的过程,在做用域链中的变量属于它的活跃对象。
On return from this algorithm we have always a value of type Reference, which base component is the corresponding variable object (or null if variable is not found), and a property name component is the name of the looked up (resolved) identifier. In detail Reference type is discussed in the Chapter 3. This.
从该计算程序返回,咱们老是有一个引用类型的值,它指向变量对象(若是找不到变量指向null)。属性名是查找到(已解析)标识符的名称组成。 Chapter 3. This详细讨论了引用类型。
Process of identifier resolution includes lookup of the property corresponding to the name of the variable, i.e. there is a consecutive examination of variable objects in the scope chain, starting from the deepest context and up to the top of the scope chain.
标识符解析过程包括查找与变量名对应的属性。i.e.在做用域链对变量对象进行连续检查。从最下面的做用域开始,直到做用域链的顶部。
Thus, local variables of a context at lookup have higher priority than variables from parent contexts, and in case of two variables with the same name but from different contexts, the first is found the variable of deeper context.
所以,查找时执行环境的局部变量比来自父执行环境的变量具备更高的优先级,万一有两个变量同名的状况但在不一样执行环境,查找的第一个是在最里面的执行变量。
Let’s a little complicate an example described above and add additional inner level:
如上所述让咱们把一个例子复杂化,在函数里面在添加一个:
var x = 10; function foo() { var y = 20; function bar() { var z = 30; alert(x + y + z); } bar(); } foo(); // 60
For which we have the following variable/activation objects, [[Scope]] properties of functions and scope chains of contexts:
为此,咱们有变量对象/活跃对象。函数有[[Scope]]属性,执行环境有做用域链:
Variable object of the global context is:
变量对象在全局执行环境:
globalContext.VO === Global = { x: 10 foo: <reference to function> };
At foo
creation, the [[Scope]]
property of foo
is:
在foo的建立,foo的[[Scope]]属性:
foo.[[Scope]] = [
globalContext.VO
];
At foo
function call, the activation object of foo
context is:
在foo函数的执行,foo执行环境的活跃对象:
fooContext.AO = { y: 20, bar: <reference to function> };
And the scope chain of foo
context is:
foo执行环境中的做用域链:
fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.: fooContext.Scope = [ fooContext.AO, globalContext.VO ];
At creation of inner bar
function its [[Scope]]
is:
建立的内部bar函数它的[[Scope]]:
bar.[[Scope]] = [
fooContext.AO,
globalContext.VO
];
At bar
function call, the activation object of bar
context is:
执行bar函数,它的执行环境的活跃对象:
barContext.AO = { z: 30 };
And the scope chain of bar
context is:
bar执行环境中的做用域链:
barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.: barContext.Scope = [ barContext.AO, fooContext.AO, globalContext.VO ];
Identifier resolution for x
, y
and z
names:
标识符解析的名字:
- "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
2.二、做用域特征
Let’s consider some important features related with Scope chain and [[Scope]]
property of functions.
让咱们来考虑函数一些重要的特征,做用域链做用域属性
2.2.一、闭包
Closures in ECMAScript are directly related with the [[Scope]]
property of functions. As it has been noted, [[Scope]]
is saved at function creation and exists until the function object is destroyed. Actually, a closure is exactly a combination of a function code and its [[Scope]]
property. Thus, [[Scope]]
contains that lexical environment (the parent variable object) in which function is created. Variables from higher contexts at the further function activation will be searched in this lexical (statically saved at creation) chain of variable objects.
闭包在ECMAScript中与函数[[Scope]]属性有关,正如咱们注意到的,[[Scope]]在函数建立时被保存,直到函数对象被销毁。事实上,闭包刚好是函数代码与其[[Scope]]属性的组合,在这个函数中建立时[[Scope]]包含了词法环境(函数的父级活跃对象)、变量都来自于上级执行环境,在进一步的函数活跃中,将会在这个(静态保存在建立)词法链的活跃对象中搜索
Examples:
var x = 10; function foo() { alert(x); } (function () { var x = 20; foo(); // 10, but not 20 })();
We see that x
variable is found in the [[Scope]]
of foo
function, i.e. for variables lookup the lexical (closured) chain defined at the moment of function creation, but not the dynamic chain of the call (at which value of x
variable would be resolved to 20
) is used.
咱们看看 在函数foo的[[Scope]]这个变量x ,i.e 在函数建立的时候,在定义的词法(闭包)链中的变量查找,可是不是动态的去调用做用域链
Another (classical) example of closure:
另外一个(经典)闭包实例:
function foo() { var x = 10; var y = 20; return function () { alert([x, y]); }; } var x = 30; var bar = foo(); // anonymous(匿名) function is returned bar(); // [10, 20]
Again we see that for the identifier resolution the lexical scope chain defined at function creation is used — the variable x
is resolved to 10
, but not to 30
. Moreover, this example clearly shows that [[Scope]]
of a function (in this case of the anonymous function returned from function foo
) continues to exist even after the context in which a function is created is already finished.
咱们在次看到 使用在函数建立中定义的词法范围链的标识符来解析----这个变量x被解析为10,而不是30。此外,这个例子清楚的显示 [[Scope]]在函数(在这种状况下,从函数foo返回匿名函数)存在,甚至在建立函数的执行环境完成以后仍然存在。
In more details about the theory of closures and their implementation in ECMAScript read in the Chapter 6. Closures.
关于闭包理论的更多细节和它们在ECMAScript中的实现,阅读Chapter 6. Closures。
2.2.二、经过构造函数的函数建立的[[Scope]]
In the examples above we see that function at creation gets the [[Scope]]
property and via this property it accesses variables of all parent contexts. However, in this rule there is one important exception, and it concerns functions created via the Function constructor.
在上面的例子中,咱们看到函数建立 获取[[Scope]]属性,经过这个属性访问全部父级执行环境的变量。然而,在这个规则里这是个重要的例外,它涉及经过构造函数建立的函数。
var x = 10; function foo() { var y = 20; function barFD() { // FunctionDeclaration 函数声明 alert(x); alert(y); } var barFE = function () { // FunctionExpression 函数表达式 alert(x); alert(y); }; var barFn = Function('alert(x); alert(y);'); /*由于Function是来自于全局的因此他不能访问函数foo里的变量。除非把y改成全局的*/ barFD(); // 10, 20 barFE(); // 10, 20 barFn(); // 10, "y" is not defined
}
foo();
As we see, for barFn
function which is created via the Function
constructor the variable y
is not accessible. But it does not mean that function barFn
has no internal [[Scope]]
property (else it would not have access to the variable x
). And the matter is that [[Scope]]
property of functions created via the Function
constructor contains always only the global object. Consider it since, for example, to create closure of upper contexts, except global, via such function is not possible.
咱们看到,对于barFn函数,经过Function构造 变量y没法访问。可是这并不意味着 函数barFn内部没有[[Scope]]属性(不然他不会 访问到变量x)。问题是[[Scope]]属性,经过Function构造在函数建立,老是只包含全局对象。自认为例如,上层执行环境建立闭包,除了全局,这样使用函数是不合理的。
2.2.三、二维做用域链查找
Also, an important point at lookup in scope chain is that prototypes (if they are) of variable objects can be also considered — because of prototypical nature of ECMAScript: if property is not found directly in the object, its lookup proceeds in the prototype chain. I.e. some kind of 2D-lookup of the chain: (1) on scope chain links, (2) and on every of scope chain link — deep into on prototype chain links. We can observe this effect if define property in Object.prototype
:
并且,重点查找做用域链的原型(若是他们是的话) 变量对象也能够考虑。----因为原型的性质在ECMAScript:若是直接在对象里未找到属性,就在原型链中查找。I.e 某种在链中的二维查找:(1)做用域链的连接 (2)以及在做用域链上的每个环节----关于原型链的深刻探讨。若是在Object.prototype中定义属性,咱们能够观察到这种效果:
function foo() { alert(x); } Object.prototype.x = 10; foo(); // 10
Activation objects do not have prototypes what we can see in the following example:
活跃对象没有原型,在下面的例子咱们能够看到:
function foo() { var x = 20; function bar() { alert(x); } bar(); } Object.prototype.x = 10; foo(); // 20
If activation object of bar
function context would have a prototype, then property x
should be resolved in Object.prototype
because it is not resolved directly in AO. But in the first example above, traversing the scope chain in identifier resolution, we reach the global object which (in some implementation but not in all) is inherited from Object.prototype
and, accordingly, x
is resolved to 10
.
若是bar函数执行环境的活跃对象有原型,而后原型x应该为对象Object.prototype,由于它不能直接在AO里肯定。可是在上面第一个例子中(//这里说的是上面代码的再上面的代码),咱们标识符解析遍历原型链,直到遍历到全局对象(在某些实现中,但不是所有)。它继承Object.prototype,所以x的值为10。
The similar situation can be observed in some versions of SpiderMokey with named function expressions (abbreviated form is NFE), where special object which stores the optional name of function-expression is inherited from Object.prototype
, and also in some versions of Blackberry implementation where activation objects are inherited from Object.prototype. But more detailed this features are discussed in Chapter 5. Functions.
能够观察到相似的状况,SpiderMokey的一些版本 命名函数表达式(缩写形式是NFE)。其中存储函数表达式任意名称的特殊对象是继承Object.propotype的,另一些版本的 关于Blackberry的实现,在那里的活跃对象继承Object.propotype。在Chapter 5. Functions 讨论了更详细的特征。
2.2.四、全局做用域链和eval执行环境
Here is not so much interesting, but it is necessary to note. The scope chain of the global context contains only global object. The context with code type “eval” has the same scope chain as a calling context.
这里没有那么有趣,但有必要注意。全局执行环境的做用域链只有一个全局对象。执行环境里'eval'代码类型有同样做用域链,做为执行的执行环境
globalContext.Scope = [
Global
];
evalContext.Scope === callingContext.Scope;
2.2.五、代码执行过程当中对做用域链的影响
In ECMAScript there are two statements which can modify scope chain at runtime code execution phase. These are with statement and catch clause. Both of them add to the front of scope chain the object required for lookup identifiers appearing within these statements. I.e., if one of these case takes place, scope chain is schematically modified as follows:
在ECMAScript 在代码执行阶段有两个语句能够修改做用域链,是with语句和catch捕获。它们都添加到范围链的前面,用于查找这些语句中出现的标识符所需的对象。I.e 若是这两个其中一个的代码执行了,伴随着做用域链的修改:
Scope = withObject|catchObject + AO|VO + [[Scope]]
The statement with in this case adds the object which is its parameter (and thus properties of this object become accessible without prefix):
这种状况下这个语句添加了对象是其参数。(所以该对象的属性变成了,没有前缀来访问)
var foo = {x: 10, y: 20}; with (foo) { alert(x); // 10 alert(y); // 20 }
Scope chain modification:
做用域链修改:
Scope = foo + AO|VO + [[Scope]]
Let us show once again that the identifier is resolved in the object added by the with statement to the front of scope chain:
让咱们再次展现 ,对范围链前面的with语句的对象中添加解析标识符:
var x = 10, y = 10; with ({x: 20}) { var x = 30, y = 30; alert(x); // 30 alert(y); // 30 } alert(x); // 10 alert(y); // 30
What happened here? On entering the context phase, “x” and “y” identifiers have been added into the variable object. Further, already at runtime code executions stage, following modifications have been made:
这里发生了什么?进入执行阶段,'x'和'y'标识符已经添加到变量对象中。进一步,已是代码执行阶段了,通过修改:
Also, a catch
clause in order to have access to the parameter-exception creates an intermediate scope object with the only property — exception parameter name, and places this object in front of the scope chain. Schematically it looks so:
另外,为了捕获参数异常,catch在中间的做用域对象建立具备原型属性--异常参数名称,在将此对象放在做用域链前面。他看起来是这样的:
try { ... } catch (ex) { alert(ex); }
Scope chain modification:
做用域链修改:
var catchObject = { ex: <exception object> }; Scope = catchObject + AO|VO + [[Scope]]
After the work of catch clause is finished, scope chain is also restored to the previous state.
catch捕获工做完成后,做用域链恢复到catch未执行的时候
三、结论
At this stage, we have considerate almost all general concepts concerning execution contexts and related with them details. Further, according to plan, — detailed analysis of function objects: types of functions (FunctionDeclaration, FunctionExpression) and closures. By the way, closures are directly related with the [[Scope]] property discussed in this article, but about it is in appropriate chapter. I will be glad to answer your questions in comments.
现阶段,咱们考虑了几乎全部关于执行环境的概念和与他们有关的细节。进一步,按计划,----函数对象的详细分析:函数类型(函数声明FunctionDeclaration,函数表达式FunctionExpression)还有闭包。顺便说闭包与本文讨论的[[Scope]]属性直接相关,闭包它在其余的章节中讨论。我很高兴在评论中回答你的问题。