1 2 3 4 5 6 7 8 9
| function foo(){ var a=2; function bar(){ console.log(a); } return bar; } var baz=foo(); baz();
|
内部函数bar()能够访问foo()的内部作用域,将bar()函数本身作为值类型传递,调用foo(),我们通常希望整个内部作用域被销毁,然而因为bar()所声明的位置拥有涵盖foo()内部作用域的闭包,使得作用域一直存活,以供bar()在任何时间之后进行引用。内部函数依然持有对外部函数的作用域的引用,这个引用就叫做闭包
总结来说:本质就是上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。
闭包的作用:
封装变量
闭包可以把一些不需要暴露在全局的变量封装成“私有变量”,如果一个大函数中有一些代码块能独立出来,我们常常把这些代码块封装在一个独立的小函数里面,独立出来的小函数有助于代码复用,如果这些小函数不需要在程序的其他地方使用,可以用闭包封闭起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var mult = (function(){ vat cache = {} var calculate = function(){ var a=1 for(var i=0,l=arguments.length;i<l;i++){ a = a*arguments[i] } } return function(){ var args = Array.prototype.join.call(arguments,',') if(args in cache){ return cache[args] } return cache[args] = calculate.apply(null,arguments) } })()
|
延长局部变量寿命
1 2 3 4 5
| var report = function(src){ var img = new Image() img.src = src } report("http://xxx.com/getUserInfo")
|
如果report函数调用结束后,img局部变量立即被销毁,如果还没来得及发起HTTP请求,此次起笔趣就会丢失掉
用闭包延长Img寿命
1 2 3 4 5 6 7 8 9
| var report = (function(){ var imgs = [] return function(src){ var img = new Image() imgs.push(img) img.src = src } })()
|
闭包和面向对象设计
对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据,通常面向对象的思想能实现的功能闭包也能实现:
1 2 3 4 5 6 7 8 9 10
| var extent = function(){ var value = 0; return { call:function(){ value++; console.log(value); } } } var extent = extent()
|
用面向对象的思想:
1 2 3 4 5 6 7 8
| var extent = { value:0, call: function(){ thie.value++; console.log(this.value) } }
|
用闭包实现命令模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| var TV = { open: function(){ console.log('open') } , close: function(){ console.log('close') }
} var createCommand = function(receiver){ var excute = function(){ return receiver.open() } var undo = function(){ return receiver.close() } return { excute:excute, undo: undo } } var setCommand = function(command){ document.getElementById('excute').onclick = function(){ command.excute() } document.getElementById('undo').onclick = function(){ command.undo() }
} setCommand(createCommand(Tv))
|
TV即命令接收者,往函数对象中预先植入命令接收者,命令接收者被封闭在闭包形成的环境中
闭包与内存管理:
使用闭包的时候容易造成循环引用,但这并非闭包的问题,浏览器的采用引用计数的垃圾回收策略,如果两个对象发生了循环引用,那么这两个对象肯都无法被回收,但是循环引用造成内存泄漏本质上也不是闭包的原因,这时需要把循环引用的变量中的其中一个设为null