Javascript 作用域链

ECMAScript 允许创建内部函数,我们甚至能从父函数中返回这些函数,每个上下文拥有自己的变量对象:对于全局上下文,它是全局对象自身;对于函数,它是活动对象。

作用域链正是内部上下文所有变量对象(包括父变量对象)的列表。此链用来变量查询。即在上面的例子中,“bar”上下文的作用域链包括AO(bar)、AO(foo)和VO(global)。

上下文如下:

activeExecutionContext = {
    VO: {...}, // or AO
    this: thisValue,
    Scope: [ // Scope chain
      // 所有变量对象的列表
      // for identifiers lookup
    ]
};

Scope = AO + [[Scope]]

作用域链涉及到函数的生命周期,函数的的生命周期分为创建和激活阶段(调用时),下面用一个例子来说明:

var x = 10;
 
function foo() {
  var y = 20;
 
  function bar() {
    var z = 30;
    alert(x +  y + z);
  }
 
  bar();
}
 
foo(); // 60

全局上下文的变量对象是:

globalContext.VO === Global = {
  x: 10
  foo: 
};

在“foo”创建时,“foo”的[[scope]]属性是:

foo.[[Scope]] = [
  globalContext.VO
];

在“foo”激活时(进入上下文),“foo”上下文的活动对象是:

fooContext.AO = {
  y: 20,
  bar: 
};

“foo”上下文的作用域链为:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:
 
fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];

内部函数“bar”创建时,其[[scope]]为:

bar.[[Scope]] = [
  fooContext.AO,
  globalContext.VO
];

在“bar”激活时,“bar”上下文的活动对象为:

barContext.AO = {
  z: 30
};

“bar”上下文的作用域链为:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
 
barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];

对“x”、“y”、“z”的标识符解析如下:

- "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

上面例子中,一个重要的属性[[scope]],[[scope]]是所有父变量对象的层级链,处于当前函数上下文之上,在函数创建时存于其中,更重要的一点是,[[scope]]在函数创建时被存储--静态(不变的),永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。


下面用一个经典例子

var x = 10;
 
function foo() {
  alert(x);
}
 
(function () {
  var x = 20;
  foo(); // 10, but not 20
})();

函数foo在执行之前已经创建了[[scope]]属性,指向父变量对象的层级链也就是global VO.所以在执行匿名函数中的foo函数,foo函数创建自己的AO,在自己AO中无法找到变量x,接着往[[scope]]属性的父变量对象查找变量x,也就是global中变量x。



总结:Javacript中,函数才能创建一个作用域块也就是上下文,在函数创建的时候就已经确定了层级链,运行时才将相应的变量赋值并形成作用域链。

备注: 这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。






你可能感兴趣的:(Javascript,javascript)