js作用域,js作用域链

对于这个我用蹩脚的英语翻译了一篇英文文章,便于以后查阅,本来这文章是有中文版的但是现在链接跳不过去QAQ。

原文链接:http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/

这篇文章不要求全部看懂,比如with没必要去纠结,因为现在with和eval是不推荐使用的,但是看完这篇文章应该要对作用域和作用域链有个概念,这将会帮助理解js的this这些。

本人英语水平垃圾和知识水平有限 ,文章是根据我的理解来翻译的,如果有翻译不恰当的地方,请务必在评论中纠正,谢谢♪(・ω・)ノ。

1、Introduction 简介

2、Definition 定义

  2.1 Function life cycle 函数生命周期

    2.1.1 Function creation 函数的创建

    2.1.2 Function activation 函数的激活

   2.2 Scope features 作用域的面纱

    2.2.1 Closures 闭包 

扫描二维码关注公众号,回复: 1020433 查看本文章

    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.

本章将详细介绍与执行环境直接相关的内容;这次,我们将提到作用域链的主题。

2、定义

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
    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.1、函数生命周期

Function life cycle is divided into a stage of creation and a stage of activation (call). Let’s consider them in detail.

函数的生命周期分为,创建阶段、激活阶段(运行)。让我们自仔细考虑一下。

2.1.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.2、函数激活

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 xy 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.2、作用域特征

Let’s consider some important features related with Scope chain and [[Scope]]property of functions.

让我们来考虑函数一些重要的特征,作用域链作用域属性

2.2.1、闭包

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.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 Functionconstructor 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.3、二维作用域链查找

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.4、全局作用域链和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.5、代码执行过程中对作用域链的影响

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 = 10, y = 10;
  • the object {x: 20} is added to the front of scope chain;
  • the met var statement inside with, of course, created nothing, because all variables have been parsed and added on entering the context stage;
  • there is only modification of “x” value, and exactly that “x” which is resolved now in the object added to the front of scope chain at second step; value of this “x” was 20, and became 30;
  • also there is modification of “y” which is resolved in variable object above; accordingly, was 10, became 30;
  • further, after with statement is finished, its special objects is removed from the scope chain (and the changed value “x” – 30 is removed also with that object), i.e. scope chain structure is restored to the previous state which was before with statement augmentation;
  • as we see in last two alerts: the value of “x” in current variable object remains the same and the value of “y” is equal now to 30 and has been changed at with statement work.

这里发生了什么?进入执行阶段,'x'和'y'标识符已经添加到变量对象中。进一步,已经是代码执行阶段了,经过修改:

  • x = 10, y = 10;
  • 对象{x: 20}添加到当前作用域链前面
  • 里面的var语句,当然没有创建任何东西。因为全部的变量已经解析过 添加在执行环境。
  • 这里只修改x的值,确切的说是修改x,现在在第二步解析了对象前面添加的作用域链。x由20变为了30。
  • 另外还有y的修改,它在上面的变量对象中被解析,因此由10变为了30。
  • 进一步,后面with语句结束,其特殊对象从作用域链中移除(并且改变的值“x”- 30也被移除。), i.e 作用域链结构恢复到with语句执行前的状态。
  • 我们看到最后两次alert:“x”在当前变量对象中的值保持不变。y在with语句执行后发生了变化,“y”现在是30。

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未执行的时候

3、结论

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]]属性直接相关,闭包它在其他的章节中讨论。我很高兴在评论中回答你的问题。

猜你喜欢

转载自www.cnblogs.com/ruoyin/p/9034294.html