在线交流

联系电话:0731-85351024 18670361250

在线联系

你还可以到这里给我们留言

全国统一服务热线

0731-85351024 18670361250

您当前所在的位置: 首页> 网站建设> 对JS闭包(Closure)的一些理解
网站建设

对JS闭包(Closure)的一些理解

更新时间:2013-05-27 17:18:16  编辑:广佳网络

Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。所以我认为理解闭包很重要,查阅了一些文档,下面是我对闭包的一些粗浅的理解…

什么是闭包?

自由变量是未绑定到特定对象的变量,而闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。

各种专业文献上的”闭包”(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。

  • 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。

  • 闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配

  • 当在一个函数内定义另外一个函数就会产生闭包

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的价值

闭包的价值在于可以作为函数对象 或者匿名函数,对于类型系统而言这就意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中、作为参数传递给其他函数,最重要的是能够被函数动态地创建和返回。

变量的作用域

变量的作用域无非就是两种:全局变量和局部变量。另一方面,在函数外部自然无法读取函数内的局部变量。这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

如何从外部读取局部变量

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。

functionf1(){
 
    varn=999;
 
    functionf2(){
        alert(n);
    }
 
    returnf2;
 
}
 
varresult=f1();
 
result(); // 999

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

怎么来理解这句话呢?请看下面的代码。

functionf1(){
 
    varn=999;
 
    nAdd=function(){n+=1}
 
    functionf2(){
        alert(n);
    }
 
    returnf2;
 
}
 
varresult=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,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包的样例

前面的我大致了解了Javascript闭包是什么,闭包在Javascript是怎么实现的。下面我们通过针对一些例子来帮助大家更加深入的理解闭包,下面共有5个样例,例子来自于JavaScript Closures For Dummies(镜像)
例子1:闭包中局部变量是引用而非拷贝

functionsay667() {
    // Local variable that ends up within closure
    varnum = 666;
    varsayAlert = function() { alert(num); }
    num++;
    returnsayAlert;
}
varsayAlert = say667();
sayAlert()

因此执行结果应该弹出的667而非666。

例子2:多个函数绑定同一个闭包,因为他们定义在同一个函数内。

functionsetupSomeGlobals() {
    // Local variable that ends up within closure
    varnum = 666;
    // Store some references to functions as global variables
    gAlertNumber = function() { alert(num); }
    gIncreaseNumber = function() { num++; }
    gSetNumber = function(x) { num = x; }
}
setupSomeGlobals(); // 为三个全局变量赋值
gAlertNumber(); //666
gIncreaseNumber();
gAlertNumber(); // 667
gSetNumber(12);//
gAlertNumber();//12

例子3:当在一个循环中赋值函数时,这些函数将绑定同样的闭包

functionbuildList(list) {
    varresult = [];
    for(vari = 0; i < list.length; i++) {
        varitem = 'item'+ list[i];
        result.push( function() {alert(item + ' '+ list[i])} );
    }
    returnresult;
}
functiontestList() {
    varfnlist = buildList([1,2,3]);
    // using j only to help prevent confusion - could use i
    for(varj = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

testList的执行结果是弹出item3 undefined窗口三次,因为这三个函数绑定了同一个闭包,而且item的值为最后计算的结果,但是当i跳出循环时i值为4,所以list[4]的结果为undefined.

例子4:外部函数所有局部变量都在闭包内,即使这个变量声明在内部函数定义之后。

functionsayAlice() {
    varsayAlert = function() { alert(alice); }
    // Local variable that ends up within closure
    varalice = 'Hello Alice';
    returnsayAlert;
}
varhelloAlice=sayAlice();
helloAlice();

执行结果是弹出”Hello Alice”的窗口。即使局部变量声明在函数sayAlert之后,局部变量仍然可以被访问到。

例子5:每次函数调用的时候创建一个新的闭包

functionnewClosure(someNum, someRef) {
    // Local variables that end up within closure
    varnum = someNum;
    varanArray = [1,2,3];
    varref = someRef;
    returnfunction(x) {
        num += x;
        anArray.push(num);
        alert('num: '+ num +
        '\nanArray '+ anArray.toString() +
        '\nref.someVar '+ ref.someVar);
    }
}
closure1=newClosure(40,{someVar:'closure 1'});
closure2=newClosure(1000,{someVar:'closure 2'});
closure1(5); // num:45 anArray[1,2,3,45] ref:'someVar closure1'
closure2(-10);// num:990 anArray[1,2,3,990] ref:'someVar closure2'

闭包的应用

Singleton 单件:

varsingleton = function() {
    varprivateVariable;
    functionprivateFunction(x) {
        ...privateVariable...
    }
    return{
        firstMethod: function(a, b) {
            ...privateVariable...
        },
        secondMethod: function(c) {
            ...privateFunction()...
        }
    };
}();

这个单件通过闭包来实现。通过闭包完成了私有的成员和方法的封装。匿名主函数返回一个对象。对象包含了两个方法,方法1可以方法私有变量,方法2访问内部私有函数。需要注意的地方是匿名主函数结束的地方的’()’,如果没有这个’()’就不能产生单件。因为匿名函数只能返回了唯一的对象,而且不能被其他地方调用。这个就是利用闭包产生单件的方法。

思考题

如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。

代码片段一。

varname = “The Window”;
 
varobject = {
    name : “My Object”,
 
    getNameFunc : function(){
        returnfunction(){
            returnthis.name;
        };
 
    }
 
};
 
alert(object.getNameFunc()());

代码片段二。

varname = "The Window";
 
varobject = {
    name : "My Object",
 
    getNameFunc : function(){
        varthat = this;
        returnfunction(){
            returnthat.name;
        };
 
    }
 
};
 
alert(object.getNameFunc()());

(完)