javascript之闭包

闭包

在ECMAScript语言里,函数是第一类对象。这就是说函数是可以像参数一样被传递给别的函数(这种情形,他们称为"funargs",简称"functional

arguments")。

函数接受了函数参数是称为高阶函数或,更接近于数学上,运算符。函数也可能从别的函数返回出来。函数从别的函数返回,那么就称为函数的值函数。

 关于函数参数和函数值有两个概念问题。这两个子问题被称为泛函参问题。要准确的解决这个泛函参问题,就引入了闭包概念。然我们详细的描述这

两个问题(我们可以看到,在ECMAScript中使用了函数的[[Scope]]属性来解决这个问题)。

第一个子问题是向上查找参数问题。它出现在当一个函数被返回到外面时,并且使用了自由变量,这个问题就会出现。

甚至于外部上下文结束之后,为了能访问到外部的上下文变量,内部函数在创建的同时就会保存在内部函数的[[Scope]]属性的父元素的作用域中。

当这个函数被激活时,这个上下文的作用域链就由当前激活对象和它的[[Scope]]属性(确切的说,这就是我们所说的):

  Scope chain = Activation object + [[Scope]]

可以看到重要的事情——在创建时候——一个函数保存了外部函数的作用域, 这是为了将来函数调用时,在查找变量时,要用到这个作用域链。

function foo(){
    var x = 10;

    return function bar(){
        console.log(x);
    };
}

//"foo" returns also a function 
//and this returned function uses
// free variable "x"
var returnedFunc = foo();

//global variable "x"
var x = 20;

//execution of the returned function
returnedFunc();  //10

 这种作用域的类型称作静态作用域。我们看到变量x是在返回的bar函数的[[Scope]]里查找到的。理论上,在上面的例子,当变量x被设置为20而

不是10,这是一个动态作用域。然而,动态作用域是不会使用的在ECMAScript。

第二个子问题是向内查找函数的参数问题。这种情况,外部上下文可能存在,但是对于解释标识符会产生歧义。问题是:哪个作用域里的标识符的

值会被使用——是在函数创建时还是在函数执行时产生的作用域呢?为了避免二义性与确立闭包,静态作用域会被使用:

// global "x"
var x = 10;

// global function 
function foo(){
    console.log( x );
}

(function(){
    // local "x"
    var x = 20;

    //there is no ambiguity,
    //because we use global "x",
    //which was statically saved in
    //[[Scope]] of the "foo" function,
    //but not the "x" of the caller's scope,
    //which actives the "funArg"

    funArg(); // 10,but not 20
})(foo); // pass "down" foo as a "funarg"

我们可以总结:一个静态作用域在使用闭包的语言里是必需的要求。然而,别的语言可能会提供动态和静态的作用域,以供程序员选择——

是否要闭包,或不要闭包。由于ECMAScript语言只有静态作用域。结论是: ECMAScript语言是完全支持闭包的,在技术实现上是通过

函数的[[Scope]]属性来达到目的的。现在我们可以对闭包给出一个正确的定义:

  闭包是一段代码块(在ECMAScript语言就是函数)和被静态/词法存储的所有外部作用域的组合。这样,通过这些保存的作用域,函数

可以很简单的引用到自由变量。

可以看到,每个函数在创建时保存了[[Scope]],因此,所有的函数是闭包在ECMAScript里。

另外值得注意的是,若干个函数可能有相同的外部作用域(这是很普遍的情况,当我们有两个内部/全局函数)。这种情况变量是存储在[[Scope]]

属性是被所有函数具有相同的外部作用域链共享的。在一个闭包里改变了变量,是可以反映到别的闭包上。

function baz(){
    var x = 1;

    return {
        foo: function foo(){ return ++x; },
        bar: function bar(){ return --x; }
    }
}

var closures = baz();
console.log( 
    closures.foo(); // 2
    closures.bar(); // 1
);

上面的代码,可以用下图所示:

这样会导致困惑,在循环创建若干个函数时。在函数内部创建的循环计数变量,当所有的函数使用了相同的计数变量,一些程序员会得不到预期值。

现在应该是很清楚为什么会这样——因为所有的函数有相同的[[Scope]],这个循环计数变量是最后一次的赋的值。

var data = [];

for( var k = 0; k < 3; k++ ){
    data[ k ] = function(){
        alert( k );
    }
}

data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2 

有若干个技术可以解决这个问题。其中之一就是提供另一个对象在这个作用域里。比如,使用另外的函数:

var data = [];

for( var k = 0; k < 3; k++ ){
    data[ k ] = (function(k){
        return function(){
            alert( k );
        }
    })(k)
}

data[0](); // 0
data[1](); // 1
data[2](); // 2
原文地址:https://www.cnblogs.com/branches/p/4887928.html