Javascript学习---3、闭包(closure)

       要成为高级 JavaScript 程序员,就必须理解闭包。当然,理解闭包必须先去理解执行环境和作用域(参考:我的前一篇文章Javascript学习---2、执行环境,作用域

  1、 作用域分配与变量访问规则

      每个函数对象都有一个内部的 [[scope]] 属性,这个属性也由对象列表(链)组成。这个内部的[[scope]] 属性引用的就是创建它们的执行环境的作用域链,同时,当前执行环境的活动对象被添加到该对象列表的顶部。当我们在函数内部访问变量时,其实就是在作用域链上寻找变量的过程。对于此部分的内容如前所述,最好先去了解我的前一篇文章Javascript学习---2、执行环境,作用域)。

     用代码再来说明一下,代码如下所示:

<script type="text/javascript">
function outer(){
    var i = 10;
    function inner(){
        var j = 100;
        alert(j);//100
        alert(i);//10
        alert(adf);
    }
    inner();
}
outer();
</script>

下图清晰的展现了上述代码的内存分配与作用域分配情况:

 Javascript学习---3、闭包(closure)

(摘自:笨蛋的座右铭)

下面来解释一下:(摘自:笨蛋的座右铭)

1.载入代码,创建全局执行环境,此时会在全局对象(window)中添加outer变量,其指向函数对象outer,此时作用域链中只有window对象.

2.执行代码,当程序执行到outer()时,会在全局对象中寻找outer变量,成功调用。

3.创建outer的执行环境,此时会新创建一个活动对象,添加变量i(赋上初始值undefined),添加变量inner,指向函数对象inner.并将活动对象压入作用域链中.并将函数对象outer的[[scope]]属性指向活动对象outer。此时作用域链为outer的活动对象+ 全局对象window.

4.执行代码,为 i 成功赋值。当程序执行到inner()时,会在函数对象outer的[[scope]]中寻找inner变量,找到后成功调用。

5.创建inner的执行环境,新建一个活动对象,添加变量j,并将该活动对象压入作用域链中,并函数对象inner的[[scope]]属性指向活动对象inner。此时作用域链为:inner的活动对象+outer的活动对象+全局对象window。

6.执行代码为j赋值,当访问i、j时成功在作用域中找到对应的值并输出,而当访问变量adf时,没有在作用域中寻找到,访问出错。

2、什么是闭包呢?

      官方的说法太学术话了,根本不是人看的!其实,闭包就是嵌套在函数里面的内部函数,该内部函数使用了其外部函数中声明的局部变量、参数或其他内部函数。当该内部函数在外部函数外被调用时,就生成了闭包。(实际上任何函数都是全局作用域的内部函数,都能访问全局变量,所以都是window的闭包)。

      另外,李松峰(《javascript高级程序设计第二版》译者)在《理解 JavaScript 闭包》中提出一个描述感觉不错,“闭包,就是封闭了外部函数作用域中变量的内部函数。但是,如果外部函数不返回这个内部函数,闭包的特性无法显现。如果外部函数返回这个内部函数,那么返回的内部函数就成了名副其实的闭包。此时,闭包封闭的外部变量就是自由变量,而由于该自由变量存在,外部函数即便返回,其占用的内存也得不到释放。”

     如果用一句话来概括闭包,我觉得应该是“闭包就是能够读取其他函数内部变量的函数!”

      通过示例来说明,如下代码所示:

Javascript学习---3、闭包(closure)

      在上述代码中,具有形成闭包的2个基本条件:第一,外层函数返回了一个内部函数;第二,该内部函数使用了外层函数中的局部变量。

     照理,执行上述代码时,外层函数的局部变量在返回时就超出了作用域,因此increment()调用无法使用才对,即使能使用每次id都是从0开始的,也不应该输出1和2,这是为什么呢?这就是闭包的作用。应用上面讲到的作用域,我们可以得到如下所示的示意图:

   Javascript学习---3、闭包(closure)

      根据该示意图,我们可以得知由于内层函数持有对外层函数的作用域,或者说内层函数引用了外层函数中的变量,这就使得在外层在执行完毕之后,Javascript的垃圾回收机制不会回收外层函数所占资源。(Javascript的垃圾回收机制:某个对象只有不再被引用,该对象才将被回收!)这就是闭包的作用!因此,在上述代码中,由于闭包的存在,使得外层函数中的变量i一直存在,所以,代码运行后,不断增加。

3、闭包的具体应用

Demo1:

  <ul id="demo">
	<li>Li 1</li>
	<li>Li 2</li>
	<li>Li 3</li>
	<li>Li 4</li>
	<li>Li 5</li>
	<li>Li 6</li>
</ul>

希望在上述Html代码结构中,每次单击li项时,弹出它的索引。代码如下:

var demo = document.getElementById('demo');
var lis = demo.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++){
    lis[i].onclick = function(){
	alert(i );
	//console.log(i); //console.log():ff控制台api,作用:在控制台打印出参数
      }
}

      上述代码在执行时,并不会产生我们想要的结果。实际上,每次单击时,都弹出的是“6”,而不是相应的i。为什么?这里就涉及到闭包和javascript的执行模型问题。这里onclick事件对应的匿名函数由于它使用了外部函数中的变量i,这就是一个典型的闭包了,该匿名函数的作用域中都保存着其外层函数的活动对象,所以每个click事件的匿名函数引用的是同一个i,而且变量i一直存在。同时,由于函数是在被执行的时候(而不是在定义的时候),会被推入一个栈环境中,才会创建自己的执行环境,在这个例子中,当用户点击的时候才会执行该函数,此时,程序已经循环完毕,i=6,所以每次都是6而不是想要的序号值。

     如何解决上述问题,得到我们想要的结果呢。方法很多,具体可以参考《理解Javascript闭包》中提高的各种方法。这里只是列出其中的2个方法

      方法1:添加一个匿名函数强制让闭包行为符合预期。代码如下所示:

var demo = document.getElementById('demo');
var lis = demo.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++){
	lis[i].onclick = function(num){ //注意:在原先的闭包外围包围了一个匿名函数
	   return function(){
			alert(num);
	   }
	}(i); //直接执行了匿名函数
}

        在上述代码中,没有直接把闭包赋值给数组,而是又定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给了数组。该匿名函数有形参num,在调用每个匿名函数时,我们传入了变量i,由于函数参数是按值传递的,所以,就会将变量i的当前值复制给num,而在这个匿名函数内部,又创建并返回一个访问num的闭包。这样一来,resut数组中的每个函数都有自己num变量的一个副本,因此可以实现想要的结果了。

      方法2:为遍历的每个元素添加自定义属性用来保存当前的索引值:

var demo = document.getElementById('demo');
var lis = demo.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++){
    lis[i].v = i;
    lis[i].onclick = function(){
	   alert(this.v);
   }
}

(注:这里还是想不明白为什么这样就行呢?)

参考资料:(学习时收集的资料)

1、http://www.cnblogs.com/fool/archive/2010/10/19/1855265.html

2、http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html#clFrmC

3、http://www.cnblogs.com/jeffwongishandsome/archive/2009/05/17/1458405.html

4、http://www.cnblogs.com/leo_wl/archive/2010/07/14/1777012.html

5、http://www.cnblogs.com/jxin/archive/2010/11/15/1877458.html

6、http://classjs.com/?p=88

7、http://www.cnblogs.com/ljchow/archive/2010/07/06/1768749.html

8、http://jibbering.com/faq/notes/closures/

9、http://www.mikkolee.com/81

10、http://hax.javaeye.com/blog/273210

11、http://www.cnblogs.com/jenry/archive/2010/12/08/1900591.html

转载于:https://www.cnblogs.com/lzm525/archive/2010/12/12/1902707.html