函数之闭包的基础----作用域链

理解闭包我觉得需要事先了解下面的概念和知识:

作用域(链),变量对象(活动对象),执行环境。

依次解释概念:

作用域:我们知道变量的 作用域就是程序源代码中定义这个变量的区域。(犀牛书)分为 全局作用域 和 局部作用域。(重复之前的:函数参数也是局部变量,它们只在函数体内有定义。)函数定义是可以嵌套的,所以就会有函数作用域。          

函数作用域:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。就是说,在函数体定义了一个变量,这个变量在这个函数体内始终有定义,并且,嵌套在该函数里的嵌套函数也可以向上访问这个变量。

看一个例子:

 1 function f(){
 2     var i = 0;
 3     function test(){
 4         // var i = 10;
 5         console.log(i);
 6     }
 7     // console.log(i);
 8     test();
 9 }
10 f();  //0

这里定义的变量i可以被里面的函数test访问。

和变量声明提前一样,在函数内声明的所有变量在函数体内始终都是可见的。

1 var scope = "global";
2 function f(){
3       //var scope;
4     console.log(scope);    //undefined
5     var scope = "local";    //defined,将local赋值给scope
6     console.log(scope);    //local
7 }

如上面的例子,函数体内scope在第5行才定义初始化,但其效果相当于在第三行定义而不初始化。所以忽略了第一行定义的全局变量。

再来看看作用域链:

红宝书里提出了几个概念:

执行环境:The execution context of a variable or function defines what other data it has access to, as well as how it should behave.Each execution context has an associated variable object upon which all of its defined variables and functions exist. This object is not accessible by code but is used behind the scenes to handle data.

变量或函数的执行环境定义了该变量或函数有权访问的其他数据,也决定了它们的各自行为。(有点和作用域差不多的感觉哈。把变量和函数能够访问到的其他数据都放在一个环境下,包括在一个对象里。就像一个圈子圈起来一样。)每个执行环境都有一个与之关联的的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象里。我们编写的代码无法访问它,但解析器在处理数据时会在后台使用它。

所以,最大的执行环境就是全局环境,也是最外面的一个。在web浏览器中,全局执行环境被认为是window对象。所以,所有全局对象和函数都是作为window对象的属性存在。并且,某个执行环境中的所有代码执行完后,该环境就被销毁,保存在其中的所有变量和函数定义也随之销毁。(但全局执行环境要到程序退出时,比如关闭,才会被销毁。)

Each function call has its own execution context. Whenever code execution flows into a function,the function’s context is pushed onto a context stack. After the function has finished executing,the stack is popped, returning control to the previously executing context. This facility controls execution flow throughout an ECMAScript program.

这段话的意思是,每个函数都有自己的执行环境,(首先,所有的环境会形成 一个环境栈)。当代码执行进入一个函数后(也就是开始执行函数里的代码了),函数的执行环境会被推进(push)这个大的环境栈中,当函数执行完成后,大的环境栈就会将该函数的环境栈挤出(pop),并将控制权交还给原先的执行环境,就是在回到原来的执行流上去了。

当代码在执行环境中执行时,变量对象的作用域链就被创建了。作用域链的目的就是为执行环境有权访问的变量和函数提供有序的访问,不让其乱套。有顺序的来。作用域链的前端(或最接近的,最里面的,最里层的)始终都是当前正在执行的代码所在的执行环境的变量对象。(执行环境其实呢,是一个比较虚的概念,而变量对象算是比较实一点的吧,因为执行环境中定义的变量和函数都保存在变量对象里。

如果执行环境是函数,则将其活动对象(activation object)作为变量对象。活动对象最开始只包含一个变量,arguments对象(在全局环境中不存在)。这就是该函数的作用域链上最前端的,最里面的了,该作用域链上下一个变量对象就是来自于包含(即外部)环境,再下一个也是来自再下一个外部环境,一层一层的逐渐的往外剥皮,知道最外面的全局环境。全局环境的变量对象就是该作用域链上最后一个对象了。

 红宝书上的例子很好:

 1 var color = "blue";
 2 function changeColor(){
 3     if(color === "blue"){
 4         color = "red";
 5     }else {
 6         color = "blue";
 7     }
 8 }
 9 
10 changeColor();

在上面的例子中,函数changeColor()的作用域链包含两个对象:自己的变量对象(其中定义者arguments对象),和全局变量对象。函数内部能访问到color变量就是因为在函数的作用域链上的对象(在全局对象)里能够找到变量color。

在局部环境中定义的变量可以在局部环境中与全局变量互换使用。

 1 var color = "blue";
 2 
 3 function changeColor(){
 4     var anotherColor = "red";
 5     function swapColor(){
 6         var tempColor = anotherColor;
 7         anotherColor = color;
 8         color = tempColor;  
 9         //这里可以访问color,anotherColor和tempColorq
10     }
11     //这里可以访问anotherColor和color,但不能访问tempColor
12     swapColor();
13 }
14 //这里就只能访问color了
15 changeColor();

这段代码有三个执行环境:全局环境,changeColor()的局部环境和swapColor()的局部环境。

全局环境中有一个变量color和函数changeColor()。changeColor()的局部环境中有变量anotherColor 和函数swapColor. swapColor()的局部环境中只有变量tempColor。changeColor()可以访问变量color,swapColor可以访问变量color和anotherColor。但全局环境和changeColor()的局部环境不能访问变量tempColor。

下图很清晰的展示了上诉关系:

不同颜色表示不同执行环境,从里到外,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境的任何变量和函数。即可以向上搜索,但不可以向下搜索。

所以函数swapColor()的作用域有三个对象:swapColor()的变量对象,changeColor()的变量对象和全局对象。swapColor()的局部环境会现在自己的变量对象中搜索变量和函数名,如果没有则向上搜索。changeColor()的作用域链只包含两个对象:它自己的变量对象和全局变量对象,因此它不能访问swapColor()的环境。

看到这里,也可以理解为什么有关于作用域链的定义说,作用域链是一群对象,或者对象列表(链表)。也有总结如下:

  • 对于javascript最顶层的代码(不包含任何函数定义内的代码),其作用域链只包含一个对象:即全局对象。
  • 对于不包含嵌套函数的函数,其作用域链包含两个对象:其自身的变量对象和全局变量对象。
  • 对于包含了嵌套函数的函数,其作用域包含至少三个对象:自身的变量对象,外层的,,,,知道全局变量对象。
原文地址:https://www.cnblogs.com/liurenxingyu/p/5130788.html