JavaScript作用域链(译)

原文: http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

作用域(scope)控制着变量和参数的可见性生命周期

通常来说,一段程序代码中所用的变量并不是总是可用的,而限定这个变量的可用性的代码范围就是这个变量的作用域。

作用域链是一个对象列表,执行上下文中出现的标识符在这个列表中进行查找。

作用域链和原型链类似:如果一个变量在函数自身的作用域(自身的变量对象)中没有找到,那么将会查找它的父函数(外层函数)的变量对象,以此类推!

通常情况下,“作用域链”是一个包含所有父(外层函数或者全局环境)变量对象以及自身变量对象的一个列表。当然了,这个作用域链也可能包含其他的对象,比如,上下文执行过程中动态加入到作用域中的对象(with对象或者特殊的catch语句对象)。

举个例子:

var x = 10;

(function foo() {
  var y = 20;
  (function bar() {
    var z = 30;

    console.log(x + y + z);     //60
  })();
})();

我们看到最内层函数bar能读到其外层函数foo以及全局作用域中的变量。

我们可以假设通过隐式的parent属性来和作用域链对象进行关联,这个属性指向作用域链中的下一个对象。利用parent概念,我们可以用下面的图来表现上面的例子(并且父变量对象存储在函数的[[Scope]]属性中):

作用域链

另外,作用域链可以通过使用with语句和catch从句对象来增强,这里就不讨论了。

在js语言里,全局作用域可以理解为window对象,记住window是对象而不是构造函数或类,也就是说window是被实例化的对象,这个实例化过程是在页面加载时由js引擎完成的。虽然我们在开发过程中不能控制这个实例化过程,但是我们不要忽略这个事实。

另外,在js语言里,任何匿名函数都是属于window对象,它们也都是在全局作用域构造时候完成定义和赋值的。但是匿名函数是没有名字的函数变量,只是在定义匿名函数时会返回其内存地址,如果此时有个变量接受了这个内存地址,那么这个匿名函数就能在其他地方调用了。

最后,说明一点: 函数本身也是一个值,它的作用域绑定函数声明时所在的环境。

var a = 1;
var f1 = function(){
    console.log(a);
}

function f2(){
    var a = 2;
    f1();
}

f2();
// 1

以上打印结果是1,而不是2。函数f1是在f2外部的全局环境中声明的,f1的作用域绑定全局环境,取不到f2内部的a。鉴于这点,有个容易犯的错误: 函数A调用函数B,却没有考虑到B并不会调用函数A内部定义的变量。 还是上面的例子,做点修改:

var f1 = function(){
    console.log(a);
}

function f2(){
    var a = 2;
    f1();
}

f2();
// Uncaught ReferenceError: a is not defined

我们看到,这里会报错,f1根本不能取到f2内部定义的变量a。

参考:
[1] http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
[2] http://ourjs.com/detail/529bc7e44c742ef031000002

views