在 Scope chain 查找变量


在〈使用 var 定义变量〉中谈过变量范围,当时说过,使用var定义的变量,作用范围基本上是在函数的范围之中,若是在全局范围,会是全局对象上的一个特性,也就是俗称的全局范围。

在〈Closure 与一级函数〉中则以置闲变量观念,从语法层面说明 Closure 的意义与作用,也可应付绝大多数的情况。

事实上,JavaScript 在查找变量时,会循着 Scope chain 逐一查找,Scope chain 可说明为何 JavaScript 没有区块范围,也是 JavaScript 中闭包的实现机制。

就结论而言,你可沿着 Scope chain 来查找变量,也就是看看函数自身的 context 对象上是否有该特性,如果没有就往外头的 context 对象看看有没有该特性。

如果要稍微深入一下 JavaScript 的 Scope chain,首先得了解一些名词。首先是 Lexical Scope,这代表着 JavaScript 源码的物理位置(Physical placement)。例如:

var x = 10;
function outer() {
    var y = 20;
    function inner() {
        var z = 30;
    }
}
outer();

在结构上,函数inner在源码中的位置是被outer包裹,而outer则是被 Global context 包裹,这样的结构在源码写下后就不变了,是属于静态的结构部份。每段 JavaScript 代码都会执行在一个 Execution context 中,对应 Lexical Scope 就是 Execution context。

像是上例中,x变量定义是在 Global execution context 中,而每个函数在调用时都会创建新的 Function execution context,有个对象用来代表 Execution context,而局部变量则是 context 对象上的特性。

查找变量就是依序在 context 对象上寻找特性,这可用来说明以下这个例子:

> function func() {
...     console.log(m);
...     var m = 10;
...     console.log(m);
... }
undefined
> func();
undefined
10
undefined
>

逐步来看的话,就是这样:

function func() {
    // 由于 Hoisting 的关系,func 的 context 对象上有 m 特性
    // 但还没指定值,因此是 undefined
    console.log(m);         
    var m = 10;       // func 的 context 对象有 m 特性且值为 10
    console.log(m);   // func 的 context 对象有 m 特性且值为 10
}

JavaScript 在查找变量时,会先在目前 context 对象上找,若找不到指定名称,则会到外层 context 对象上找,若找不到,就再到更外层 context 对象找,直到全局对象为止,这样的顺序形成变量查找的 Scope chain。

再来看局部变量覆盖全局变量的例子:

var x = 10;
function func() {
    var x = 20;
    console.log(x);
}

以 Scope chain 来解释的话,在func函数中查找x时,是先查找func的 context 对象上有无x特性,因此对应的是 20 的值。

再来看 Closure 的例子:

function doSome() {
    var x = 10;
    function f(y) {
        return x + y;
    }
    return f;
}

在内部的f函数中 context 对象上找有无x特性时,并没有找到,于是在包裹fdoSome调用对象上查找有无x,此时找到了。

可以这么说,在 JavaScript 中,所有的变量,都是某个对象上的特性。

附带一提的是,自行使用new创建的Function,如果有查找变量,一定查找全局对象中的特性。例如:

> var x = 10;
undefined
> function func() {
...     var x = 20;
...     var f = new Function('return x;');
...     return f();
... }
undefined
> func();
10
>




展开阅读全文