作用域链与闭包

来源:互联网 发布:同城o2o源码 编辑:程序博客网 时间:2024/03/28 23:30

作用域链 

    执行环境(execution context)定义了变量或函数有权访问的其它数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。全局执行环境是最外围的一个执行环境,在web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。全局执行环境直到程序退出时才被销毁。
    每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境退出,把控制权返回给之前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域中的下一个对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样一直延续到全局执行执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。标识符的解析是沿着作用域链从内向外一级一级的搜索。
    例如,下面这段代码:
 function foo() {         var val = 'hello';         function bar() {            function baz() {            window.val = 'world;'            }       baz();       console.log(val); //=> hello  }  bar();}foo();
    终端输出的结果是hello。
    接下来我们简单的分析一下这个过程,在全局执行环境中,如图1.1所示
图 1.1

    在全局执行环境中,它的变量对象包括全局变量val(baz()函数中定义的全局变量),foo()函数以及this(即window对象),就像之前提到过的,在全局执行环境中并不包含arguments对象。
    在foo函数的执行环境中,如图1.2所示

图 1.2

    在foo()函数的执行环境中,可以看到包含arguments对象、val变量(局部变量)、bar()函数以及this对象。
    在bar()函数的执行环境中,包含arguments对象、baz()函数以及this对象,如图1.3所示。

图 1.3

    在baz()函数的执行环境中,只包含了arguments对象和this对象,如图1.4所示。

图 1.4

    在图1.5中,我们看到,从全局执行环境开始,最后到baz()函数的执行环境,依次被放到环境栈中。
图 1.5

    函数baz()的作用域链包含四个对象:它自己的变量对象 、bar()函数的变量对象、foo()函数的变量对象和全局环境的变量对象。在bar()函数中输出变量val的值,首先它会在自己执行环境中搜索变量val,如果不存在则在上一级的执行环境中搜索变量val,最终才到全局环境中搜索val 的值(window.val = 'world'定义的是全局变量) ,所以最终输出的值是hello。以上代码共涉及到4个执行环境:全局环境、foo()的局部环境、bar()的局部环境和baz()的局部环境。

闭包

    闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。以下面这个计时器为例:
<!doctype html><html lang="en"> <head>  <meta charset="UTF-8">   <script src="js/jquery-1.10.2.js"></script>   <script>  $(function(){  var local=1;  window.setInterval(function(){  $("#display").append('<div>At '+new Date()+' local='+local+'</div>');    local++;  },3000);  })  </script> </head> <body>  <div id="display"></div> </body></html>

    在这个例子中,红色标注的那四行是内部函数的代码,这个内部函数访问了外部函数中的变量local。之所以能够访问这个变量,是因为内部函数的作用域链中包含了外部环境的作用域。如上一节所述,当某个函数第一次被调用时,会创建一个执行环境以及相应的作用域链,并把作用域链复制给一个特殊的内部属性(即[scope])。然后,使用this、arguments和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位...........直至作为作用域链终点的全局执行环境。来看下面的这个例子:
function compare(value1,value2){    if(value1<value2){      return -1;    }   else{     return 1;    }}var result=compare(5,10);

    上面的代码中先定义了compare( )函数,然后又在全局作用域中调用了它。当第一次调用compare( )时,会创建一个包含this、arguments、value1和value2的活动对象。全局执行环境的变量对象在compare执行环境的作用域链中则处于第二位。图2.1展示了compare( )函数执行时的作用域链。

图 2.1
    在创建compare( )函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[Scope]属性中。当调用compare( )函数时,会为函数创建一个执行环境,然后通过复制函数的[scope]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象被创建并被推入执行环境作用域链的前端。对于这个例子中,其作用域链包含两个变量对象:本地变量对象和全局变量对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只是引用但不实际包含变量对象。
看下面的这个例子:
function createComparisonFunction(propertyName) { return function(object1, object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName];  if (value1 < value2){ return -1; } else if (value1 > value2){ return 1; } else { return 0; } };} 

    红色标注的是内部函数中的代码。在另一个函数内部定义的函数会将外部函数的活动对象添加到它的作用域链中。因此,在 createComparisonFunction()函数内部定义的匿名函数的作用域链中,实际上将会包含外部 函数 createComparisonFunction()的活动对象。当下面的代码执行时,
   var compare = createComparisonFunction("name");   var result = compare({ name: "Nicholas" }, { name: "Greg" });
    在匿名函数从 createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()中定义的所有变量。更为重要的是,createComparisonFunction()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当 createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁,图2.2展示了外部函数和匿名函数的作用域链

图 2.2
0 0
原创粉丝点击