深入理解闭包

一、变量作用域以及生存周期

对于全局变量来说,全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量。
而对于在函数内用 var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了 它们的价值,它们都会随着函数调用的结束而被销毁。

var func = function(){     
var a = 1;     
return function(){        
  a++;        
  alert ( a );
 } 
};
var f = func();
f();    // 输出:2 
f();    // 输出:3 
f();    // 输出:4 

var f = func()时,f 返回了一个匿名函数的引用,它可以访问到 func() 被调用时产生的环境,而局部变量 a 一直处在这个环境里。既然局部变量所在的环境还能被外界 访问,这个局部变量就有了不被销毁的理由。在这里产生了一个闭包结构,局部变量的生命看起 来被延续了

这就是 闭包会内存泄露的原因。

二、封装变量

闭包可以帮助把一些不需要暴露在全局的变量封装成“私有变量”。假设有一个计算乘积的 简单函数:

var mult = function(){    
 var a = 1;     
 for ( var i = 0, l = arguments.length; i < l; i++ ){       
   a = a * arguments[i];    
  }     
  return a; 
};

mult 函数接受一些 number 类型的参数,并返回这些参数的乘积。

现在我们觉得对于那些相同 的参数来说,每次都进行计算是一种浪费,我们可以加入缓存机制来提高这个函数的性能:

var cache = {};
var mult = function(){     
 var args = Array.prototype.join.call( arguments, ',' );    
 if ( cache[ args ] ){         
   return cache[ args ];    
  }
  var a = 1;    
  for ( var i = 0, l = arguments.length; i < l; i++ ){         
  		 a = a * arguments[i];    
  }
  return cache[ args ] = a; 
 };

可以看到 cache 这个变量仅仅在 mult 函数中被使用,与其让 cache 变量跟 mult 函数一起平行 地暴露在全局作用域下,不如把它封闭在 mult 函数内部,这样可以减少页面中的全局变量,以 避免这个变量在其他地方被不小心修改而引发错误。

var mult = (function(){    
 var cache = {};    
 return function(){        
   var args = Array.prototype.join.call( arguments, ',' );         
   if ( args in cache ){             
    return cache[ args ];         
   }         
   var a = 1;         
   for ( var i = 0, l = arguments.length; i < l; i++ ){             
    a = a * arguments[i];         
   }         
   return cache[ args ] = a;     
  } 
})();

提炼函数是代码重构中的一种常见技巧。

这样就可以封装起来。进行独立的小函数。

三、延续变量寿命

例如以下代码

var report = function( src ){     
var img = new Image();     
img.src = src; 
};
report( 'http://xxx.com/getUserInfo' )

通过查询后台的记录我们得知,因为一些低版本浏览器的实现存在 bug,在这些浏览器 下使用 report 函数进行数据上报会丢失 30%左右的数据,也就是说,report 函数并不是每一次 都成功发起了 HTTP 请求。

丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数的 调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求 就会丢失掉

解决:

 var report = (function(){     
 	var imgs = [];     
	 return function( src ){         
 		var img = new Image();         
 		imgs.push( img );         
 		img.src = src;     
 	} 
 })()

四、闭包与内存管理

PS: 垃圾回收机制的俩个方式: 标记清除 与 引用计数。

闭包是一个非常强大的特性,但人们对其也有诸多误解。一种耸人听闻的说法是闭包会造成 内存泄露,所以要尽量减少闭包的使用。
局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境 中,那么这个局部变量就能一直生存下去。从这个意义上看,闭包的确会使一些数据无法被及时 销毁。使用闭包的一部分原因是我们选择主动把一些变量封闭在闭包中,因为可能在以后还需要 使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并 不能说成是内存泄露。如果在将来需要回收这些变量,我们可以手动把这些变量设为 null。

跟闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作 用域链中保存着一些 DOM 节点,这时候就有可能造成内存泄露。但这本身并非闭包的问题,也 并非 JavaScript 的问题。在 IE 浏览器中,由于 BOM 和 DOM 中的对象是使用 C++以 COM 对象 的方式实现的,而 COM 对象的垃圾收集机制采用的是引用计数策略。

在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用 造成的内存泄露在本质上也不是闭包造成的。
同样,如果要解决循环引用带来的内存泄露问题,我们只需要把循环引用中的变量设为 null 即可。将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运 行时,就会删除这些值并回收它们占用的内存。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值