[JS]理解闭包

执行环境和作用域

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返还给之前的执行环境。
当代码在一个环境中执行时,会创建变量对象的一个作用域链,保证对执行环境有权访问的所有变量和函数的有序访问。作用域的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。[1]

var color0="blue";

function ChangeOnce(){
    var color1 = "yellow";

    function ChangeTwice(){
        var color2 = "red";
        color1 = color0;
        color0 = color2;
        //这里能访问color0\color1\color2
    }
    //这里能访问color0\color1
}
//这里只能访问color0

以上共涉及三个执行环境:全局环境、 changeOnce() 的全局环境和 changeTwice() 的全局环境。内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部的任何变量和函数。

函数内部声明变量时,一定要使用 var 命令,否则,该变量是一个全局变量!

延长作用域链

虽然执行环境的类型总共只有两种:全局和局部,但可以通过其他办法来延长作用域链。当执行流进入下列任一语句时:作用域链就会延长。

  • try-catch语句的catch块;
  • with语句
function Name(){
    var firstName = "Smith";

    with(firstName){
        var name = firstName + " " +" Whale"
    }
    return name;
}

闭包

当外部需要访问内部函数或者变量时该怎么办呢?这是就需要用到 闭包

闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。——阮一峰的博客

闭包最大的用途有两个:
- 读取函数内部的变量
- 让变量值始终保持在内存中

function f1(){
    var n = 999;
    nAdd = function(){ n+=1}
    function f2(){
        alert (n);
    }
    return f2;
}
var result = f1();
result();    //999
nAdd();
result();    //1000

在上面的代码中, result 实际上就是闭包函数 f2 。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。——阮一峰的博客

闭包与变量

闭包只能取得包含函数中任何变量的最后一个值。如:

function createFunciton(){
    var result = new Array();

    for(var i=0; i < 10; i++>){
        result[i] = funciton (){
            return i;
        }
    }
    return result;
}

这个函数返回一个函数数组。表面上看,似乎每个函数应该返回自己的索引值,即位置0的函数返回0,位置1的函数返回1,以此类推。但实际上,每个函数都返回10。因为每个函数的作用域中都保存着 creationFunction() 函数的活动对象,所以它们引用的都是同一个变量 i 。当 creationFunction() 函数返回后,变量 i 的值是10,所以在每个函数内部i的值都是10.但是我们可以通过创建另一个匿名函数强制让闭包的行为符合预期。如下:

function createFunciton(){
    var result = new Array();

    for(var i=0; i < 10; i++>){
        result[i] = funciton (num){
            return function(){
                return num;
            };
        }(i);
    }
    return result;
}

在这个函数中,定义了一个匿名函数,并立即执行匿名函数的结果赋给数组。这里的匿名函数有一个参数 num ,也就是最终的函数要返回的值。在调用匿名函数时,传入的变量 i 的当前值按值传递赋值给参数 num 。在这个匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来, result 数组中的每一个函数都有了自己的 num 变量的一个副本,因此可以返回各自不同的数值。

[1]:《Javascript高级程序设计》(第3版) [美]Nicholas C.Zakas著 李松峰 曹力译

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章