zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    const与readonly
    JQuery Tooltipster
    Log4Net使用
    asp.net mvc 4 beta 版已发布
    控件属性
    C# 获取当前路径
    对toLocaleString()、toString()、valueOf()的理解
    靶场练习3CSRF攻击
    计算字符串长度
    Android ListView 自定义适配器
  • 原文地址:https://www.cnblogs.com/branches/p/4887928.html
Copyright © 2011-2022 走看看