现在的位置: 首页 > 综合 > 正文

终于理解了Javascript的闭包概念

2013年12月08日 ⁄ 综合 ⁄ 共 1548字 ⁄ 字号 评论关闭

原文章:

《JavaScript世界的一等公民 - 函数》

http://software.intel.com/zh-cn/articles/javascript-first-class-citizen-function/?cid=sw:prccsdn229032

 

其中2.3节非常清晰明了的解释了闭包。以前从其他文章看到的解释都不是很明了,看了2.3节终于明白了。

摘录如下:

2.2 (摘一部分,因为2.3会涉及到)

 
function generator() {  
  var i = 0;  
  return function() {  
    return i++;  
  };  
}  
  
var gen1 = generator(); // 得到一个自然数生成器  
var gen2 = generator(); // 得到另一个自然数生成器  
var r1 = gen1(); // r1 = 0  
var r2 = gen1(); // r2 = 1  
var r3 = gen2(); // r3 = 0  
var r4 = gen2(); // r4 = 1  

上面的代码展示了把函数作为返回值的示例,generator是一个自然数生成器函数,
返回值是一个自然数生成函数。
每次调用generator时都会把一个匿名函数作为结果返回,这个匿名函数在被实际调用时依次返回每个自然数。
在generator里的变量i在每次调用这个匿名函数时都会自增1,这其实就是一个闭包。
下面我们来介绍一下闭包.

2.3 闭包(Closure)

闭包(Closure)并不是一个新鲜的概念,很多函数式语言中都使用了闭包。在JavaScript中,当你在内嵌函数中使用外部函数作用域内的变量时,就是使用了闭包。用一个常用的类比来解释闭包和类(Class)的关系:类是带函数的数据,闭包是带数据的函数。
闭包中使用的变量有一个特性,就是它们不在父函数返回时释放,而是随着闭包生命周期的结束而结束。比如像上一节中generator的例子,gen1和gen2分别使用了相互独立的变量i(在gen1的i自增1的时候,gen2的i并不受影响,反之亦然),只要gen1或gen2这两个变量没有被JavaScript引擎垃圾回收,他们各自的变量i就不会被释放。在JavaScript编程中,不知不觉就会使用到闭包,闭包的这个特性在带来易用的同时,也容易带来类似内存泄露的问题。例如:
var elem = document.getElementById('test');
elem.addEventListener('click', function() {
  alert('You clicked ' + elem.tagName);
});

这段代码的作用是点击一个结点时显示它的标签名称,它把一个匿名函数注册为一个DOM结点的click事件处理函数,函数内引用了一个DOM对象elem,就形成了闭包。这就会产生一个循环引用,即:DOM->闭包->DOM->闭包...DOM对象在闭包释放之前不会被释放;而闭包作为DOM对象的事件处理函数存在,所以在DOM对象释放前闭包不会释放,即使DOM对象在DOM tree中删除,由于这个循环引用的存在,DOM对象和闭包都不会被释放。可以用下面的方法可以避免这种内存泄露:

var elem = document.getElementById('test');
elem.addEventListener('click', function() {
  alert('You clicked ' + this.tagName); // 不再直接引用elem变量
});

上面这段代码中用this代替elem(在DOM事件处理函数中this指针指向DOM元素本身),让JS运行时不再认为这个函数中使用了父类的变量,因此不再形成闭包。

闭包还会带来很多类似的内存泄露问题,只有在写代码的时候着重注意一下闭包,尽量避免此类的问题产生。

抱歉!评论已关闭.