日期:2014-05-16  浏览次数:20377 次

{{JS}}函数作用域和闭包
1.词法作用域
Javascript中的函数是通过词法来划分作用域的,而不是运态地划分作用域的,即“在Javascript中,函数运行在定义它们的作用域,而不是运行在执行它们的作用域中”。这句话可理解为:
    当使用function定义一个函数时,当前作用域被保存起来,并且成为函数内部状态的一部分。在最顶级,使用域链仅由全局对象组成,而并不和词法作用域相关。然而当定义一个嵌套的函数时,作用域链就包括外围的包含函数。这意味着嵌套的函数可以访问包含函数的所有参数和局部变量。
    
注:尽管当一个函数定义时,作用域链便固定了,但作用域链中定义的属性还没有固定。作用域链是“活的”,并且函数在被调用时可以访问当前的绑定。

2.调用对象
当JS解释器调用一个函数时,它首先将作用域设置为定义函数的时候起作用的那个作用域链接着它在作用域链的前面添加一个新的对象,称为调用对象(call object),调用对象用一个名为arguments的属性来初始化,这个属性引用了函数的Arguments对象。函数的命名参数添加到调用对象的后面。而用var语句声明的任何局部变量也都定义在这个对象中。既然这个调用对象位于作用域链的前端,局部变量,函数参数及Arguments对象都在函数内的作用域中。当然,这也意味着它们可以隐藏作用域链更上层的任何同名属性。

注:和arguments不同,this是一个关键字,而不是调用对象的一个属性。

3.作为名字空间的调用对象
有时定义一个只是创建调用对象的函数,这个调用对象充当一个临时的名字空间,可以在该名字空间中定义变量并创建属性,而不会破坏全局的名字空间。如下:
(function() {
    // todo : 
})();

4.作为闭包的嵌入函数
Javascript允许嵌入的函数,允许函数用作数据,并且使用词法作用域,这些因素相互作用可以产生惊人的效果。比如考虑一个定义在函数f中的函数g。当f被调用时,作用域链包含了对f的这一调用的调用对象,后边是全局对象。g定义在f中,因此这个作用域链保存为g的定义的一部分。当g被调用时,作用域链包含3个对象:它自己的调用对象,f的调用对象及全局对象。

1)当嵌入的函数在它们定义的同一词法作用域中调用时,它们是很好理解的。如下
var a = "global";
function f() {
    var a = "local";
    function g() { console.log(a); }
    g();
}
f();// local

2)考虑如下代码,其中包含了一个函数,它返回一个嵌套的函数。如下代码每次调用makefunc函数时它都返回一个函数。返回的函数的代码都一样。但它所创建的作用域略有不同,因为外围函数的参数值在每次调用时都不同,即外围函数的每次调用的作用链上有一个不同的调用对象。
function makefunc(x) {
    return function() { return x; }
}

var a = [makefunc(0), makefunc(1), makefunc(2)];
console.log(a[0]());    // 0
console.log(a[1]());    // 1
console.log(a[2]());    // 2
这段代码的结果正是可以从严格应用词法作用域规则所期待的:函数在它所定义的作用域中执行。然而这些结果令人吃惊的原因是:当定义了局部作用域的函数退出时期待局部作用域能够终止并退出。当一个函数被调用时,就为它创建一个调用对象并放置到作用域链中。当函数退出时,调用对象也从作用域链中移除。当没有涉及嵌套的函数时,作用域链是对调用对象的惟一的引用。当对象从链中移除了,也就没有对它的引用了,最终通过对它的垃圾收集而完结。

但是,嵌套函数改变了这一情景。如果创建一个嵌套函数,这个函数的定义引用了调用对象,因为调用对象在这个函数所定义的作用域链的顶端。可是当嵌套函数只在外围函数的内部调用时,那么对嵌套函数的惟一引用在调用对象中。当外围函数返回时,嵌套函数引用了调用对象,并且调用对象引用了嵌套函数,但是没有其它东西引用它们二者,因此对这两个对象都可以进行垃圾收集了。

如果把嵌套函数的引用保存到一个全局作用域(另一个作用域)中,情况又不同了。使用嵌套的函数作为外围函数的返回值,或者把嵌套的函数存储为某个其它对象的属性。此时,有一个对嵌套函数的外部引用,并且嵌套函数将它的引用保留给外围函数的调用对象。结果是,外围函数的一次特定调用的调用对象仍然存在,函数的参数和局部变量的名字和值在这个对象中得以维持。Javascript代码不会以任何方式直接访问调用对象,但它所定义的属性是对嵌入函数任何调用的作用域链的一部分。(注:若一个外围函数存储了两个嵌套函数的全局引用,这两个嵌套函数共享同一个调用对象,并且,一个函数的一次引用所做出的改变对于另一个函数的调用来说是可见的。)

Javascript函数是将要执行的代码以及执行这些代码的作用域构成的一个综合体,这种代码和作用域的综合体称为闭包。所有的Javascript函数都是闭包。而当一个嵌套函数被导出到它所定义的作用域时,这种闭包才是最有趣的。所以当一个嵌套函数以这种方式使用时,我们明确地称之为闭包。

如下闭包的示例:
var uniqueId = (function() {
    var id = 0;
    return function() { return id++; };
})();

//模拟私有属性
function makeProperty(o, name, predicate) {
    var value;
    o["get" + name] = function() { return value; };
    o["set" + name] = function(v) {
        if(predicate && !predicate(v)) {
            throw new Error("set" + name + ": invalid value " + v);
        } else {
            value = v;
        }
    }
}