JavaScript的闭包
每次回顾知识点都有不同的收获,对于前端程序原来说,闭包是个难懂又必须弄懂的概念。闭包的形成与变量的作用域及变量的生存周期密切相关。在此之前 先简单了解下这两个知识点
- 变量的作用域
变量的作用域,就是指变量的有效范围。就像我们常谈到的 在函数中声明的变量 在函数内有效 这就是这个变量的作用域
- 当在函数中声明一个变量的时候,如果该变量前没有带上关键字var 这个变量就会变成全局变量,
- 另一种情况是用var关键字在函数中声明变量,这时候的变量 即是局部变量 函数内部才能访问到,在函数外是无法访问的
在javascript中,函数可以用来创建作用域。函数内的可以访问到函数外部的数据,而外部无妨访问到函数内部的数据。那么在函数中搜索一个变量的时候,会先在变量的局部作用域中查找,如果没有的话,会往外层的作用域 一层一层的查找下去,直到全局对象为止,这个一层往外的链式查找 我们称之为作用域链。变量的查找及作用域链都是从内到外而非从外到内的。
var a= 1
var fun1 = function(){
var b = 2
var fun2 = function (){
var c = 3
alert(b) // 2
alert(a) // 1
}
fun2()
alert(c) // c is not defined
}
fun1()
- 变量的声明周期
对于全局变量来说,全局变量的生命周期是永久的,除非我们主动的销毁这个变量。
而对于函数内的声明的变量来说,一般在退出函数时,这些局部变量即将失去价值,会随着函数的调用的结束而被销毁 回收!!
有这么个特需情况:
var func = function(){
var a = 1
return function(){
a++
alert(a)
}
}
var f=func()
f() //2
f() //3
f() //4
f() //5
跟我们之前的理论相反,在推出函数后,局部变量并没有被注销回收,类似 在某个地方存活着。
这是因为当我们执行 var f = func()时,f返回的是个匿名函数的引用,它可以访问 func()产生的环境变量a,而局部变量a 一直处于这个环境里。既然局部变量所在的环境还能被外界访问,这个局部变量就有了不被销毁的理由。在这里产生了一个闭包结构,局部变量的生命被延续了!!!
在我们工作中有许多地方用到闭包 让我们完成许多奇妙的工作。有这么个案例:页面上有五个一模一样的按钮,我们通过循环绑定click事件的方式,点击第一个 打印0 第二个1 依次类推。。
如果就简单的通过循环 for循环 进行绑定他们的索引值的话 结果发现 打印的都是5
解决方法:就是闭包 使用闭包 将每次循环索引值封闭起来。当点击事件触发的时候 顺着作用域链中从内到外查找,分别就是 0 、1、 2、 3、 4
闭包的应用
- 封装变量
闭包可以帮助把一些不需要暴露在全局的变量封装成 私有变量。
这么个简单的计算函数
var mult = function (){
var a = 1;
for(var i= 0,l = arguments.length;i<l;i++){
a = a * arguments[i]
}
return a
}
nult函数会接受一些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函数内部,在一定程度上减少页面中的全局变量,也可避免这个变量在其他地方被不小心修改er引发的错误
var mult = (function(){
var cache = {}
return 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
}
})()
这种提炼代码也是代码重构中最常见的一种技巧。
如果在大函数中有一些代码块能够独立出来,我们常常吧这些代码块封装在独立的小函数里面;独立出来的小函数有助于代码的复用,有良好的命名的话 本身也起着注释的作用。
如果这些小函数不需要在程序的其他地方使用,最好是把它们用闭包封闭起来
var mult = (function(){
var cache = {}
var calculate = function(){
var a = 1;
for(var i= 0,l = arguments.length;i<l;i++){
a = a * arguments[i]
}
return a
}
return function (){
var args = Array.prototype.join.call(arguments,',')
if(cache[args]){
return cache[args]
}
return cache[args] = calculate.apply(null,arguments)
}
})()
- 延续局部变量的寿命
例如 img对象用于进行数据上报
var report = function( src ){
var img = new Image()
img.src = src
}
report('baidu.com')
上面代码在一些低版本浏览器的实现上存在bug,report 函数会丢失数据,并不是每次report函数都能成功的发送http请求。丢失的原因是 img是report的局部变量,当report函数执行完毕后 img局部变量会进行销毁,从而导致数据丢失的问题
修复下:
var report = (function( src ){
var imgs = []
return function(src){
var img = new Image()
imgs.push(src)
img.src = src
}
})()
闭包与内存管理
- 闭包是一个非常强大的特性。也存在一些说法 闭包会造成内存泄漏,要尽量少用不用闭包。
- 局部变量本来应该在函数推出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。在某种意义上看,闭包会使一些数据无法被及时销毁。
- 使用闭包的一部分原因是我们主动把一些变量封闭在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局变量中,对内存的影响是一样的,这个也不能说成是内存泄漏。如果在将来需要回收这些变量,我们可以手动的把它们设成null
- 使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,这时候就有可能造成内存泄漏。在IE浏览器中 由于浏览器BOM和DOM的实现原理 原因,出现上面的循环引用的问题时 就会出现内存泄漏的问题。。---同样的解决的方法是 在把循环引用的变量设成null 这样就会切断变量与他之前引用的值之间的连接,下次垃圾回收机制执行时,就会把这些值所占的内存回收
参考文献:JavaScript设计模式与开发实践