闭包浅析


一、闭包印象

闭包是JavaScript里绕不开的话题,本文旨在让你理解闭包,阐述闭包的一些特性防止不知不觉掉坑,并在适当的条件下利用好这些特性。
关于闭包的权威定义,犀牛书上说得很透彻了(其实我看得有些晕),阮一峰老师学习Javascript闭包(Closure)和耗子哥理解Javascript的闭包讲的也深入浅出,我这里参考耗子哥的定义说明我的理解:定义在一个函数内部的函数就可以看做是闭包。来一段代码:

//代码一
var a = "gloable variable";
var F = function(){
    var b = "local variable";
    function N(){   //闭包
        var c = "inner variable";
        return b;   //对父级作用域的引用
    }
    return N;
}

var local = F();
local();    //输出:local variable

正常情况下全局作用域是无法访问局部变量b的(作用域链原理),但是利用闭包就可以突破作用域链的限制。在代码一中,可以看成通过 var local = F() 将N变成了一个全局作用域中的函数。犀牛书上的一种解释为这是在不同的上下文环境分别定义(在函数F的作用域定义)和执行(在全局环境执行)函数所发生的微妙现象。

二、分析闭包

常说每个函数都可看做是一个闭包,当然没错,可是个人观点觉得这样的解释闭包没有实际意义,也没有说清楚闭包的特性,仅能说明闭包是一种常见现象。
个人认为,闭包的一个重要特性在于,内部函数在定义时是绑定了其所在的作用域本身的,这种绑定导致函数内部的变量值会随着作用域自身的变化而变化,而非内部函数在被定义或父级变量被返回时变量的值。举个例子:

function F(param){
    function inner(){   //内部函数被定义
        return param;   //父级变量被返回
    }
    param++;    //在与inner绑定的作用域修改param的值
    return inner;   
}
var a = F(123);
a();    //输出:124

在F中,定义和变量被返回都发生在修改变量之前,可见闭包并不会“记录”父级变量的值,而是由于这种绑定关系而忠实地反应变量实际的值。简单一点说,这里的inner在被调用时才会通过绑定关系去读取param的值,其他时候闭包并不关心param的值。

三、关于闭包的坑

既然闭包很常见,若不注意我们很可能误用了他的特性。循环中的闭包就是经常会碰到的坑。

var F = function(){
    var a = [];
    for(var i = 0; i < 3; i++){
        a[i] =function(){   //这里定义了三个闭包
            return i;
        }
    }   
    return a;
}

var arr = F();
arr[0]();
arr[1]();
arr[2]();   //均输出:3

如前所述,闭包在被最后调用前都不会去读取i的值,仅仅是保留了对i的引用关系,所以最后被调用时实际读取的都是同一个i值。好吧,如何防止这种不希望的现象出现呢?

var F = function(){
    var a = [];
    for(var i = 0; i < 3; i++){
        a[i] = function(x){ //即时函数读取i值给x
            return function(){
                return x;
            }
        }(i);
    }   
    return a;
}

var arr = F();
arr[0]();   //输出:0
arr[1]();   //输出:1
arr[2]();   //输出:2

通过即时函数我们在每次给数组赋值时读取i的实际值,这样就绕开了闭包的限制。

四、 闭包的实际应用

闭包最常用的地方就是setter和getter了。
假设有一个内部值,我们不能让其暴露在全局环境中以防止其被随意修改,只能通过我们规定的途径修改,当然也可以在这些途径中加入一些验证机制。举个例子:

var wenzi = (function(){
    var inner = "meta-d"; //内部变量
    var getValue = function(){
        return inner;
    }
    var setValue = function(val){
        if(typeof val == "string"){ //简单验证
            inner = val;
        }
        console.info(inner);
    }

    return {
        getValue : getValue,
        setValue : setValue
    }
})();
wenzi.getValue();   //输出:meta-d
wenzi.setValue("test"); //输出:meta-d
wenzi.getValue()   //输出:test

除此外,闭包可用的地方还有很多,很多时候我们用了闭包但是自己并没有意识到。但万变不离其宗,了解闭包的原理才是正道。还可参考司徒大神的文章javascript的闭包
行文仓促,如有不妥之处,还请拍砖,轻拍,谢谢!

原文地址:https://www.cnblogs.com/mengda1027/p/4595922.html