简单的说闭包就是函数里面的函数,《JavaScript高级程序设计》里是这样定义的javascript
闭包是指有权访问另外一个函数做用域中的变量的函数。html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>lzhTest</title> </head> <body> <ul> <li>0</li> <li>1</li> </ul> <script> var lis = document.getElementsByTagName("li"); for(var i = 0; i < lis.length; i++){ lis[i].onclick = function(event){ alert(i); } } </script> </body> </html>
分别点击 li,alert什么?答案均是 2. 为何呢?咱们接着往下看前端
函数被调用时会建立一个执行环境和做用域链 (scope chain),做用域链中每一个元素都指向一个活动对象或变量对象 (执行环境中定义的全部变量和函数都保存在这个对象中,包括 this、arguments),函数执行完毕,做用域链被销毁,若是这时相应的变量对象没有被引用,则变量对象占用的空间会被释放。
好比上面题目中的做用域链是这样的:
java
匿名函数1
和 匿名函数2
是两个事件处理函数,从图中能够看出,在做用域链的最前端(即下标为0)对应的活动对象中,是不存在 i
的,i
在全局变量对象中,点击的时候,须要往做用域的上层查找 i
,因而就找到了全局变量对象中的 i
,由于点击的时候,i
早已增长成为 2,因此 alert 的 i
均为 2。jquery
直接看代码:面试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>lzhTest</title> </head> <body> <ul> <li>0</li> <li>1</li> </ul> <script> var lis = document.getElementsByTagName("li"); var helper = function(i){ return function(event) { alert(i); } } for(var i = 0; i < lis.length; i++){ lis[i].onclick = helper(i); } // 参考自:《JavaScript语言精粹》 </script> </body> </html>
对应的做用域链及活动对象:
浏览器
匿名函数1
,(根据《JavaScript高级程序设计》介绍:函数在调用时生成做用域链和活动对象,但闭包被返回时,就会生成做用域链和活动对象(中文第3版 P180 line7),个人理解并非这样的),返回的 匿名函数1
引用着 helper1 中的i,helper(1) 执行完毕,以后 helper(1)的执行环境和做用域链销毁,可是 helper(1) 的活动对象还在,由于 匿名函数1
的做用域链还在引用着它(按照个人理解应该是 匿名函数1
还引用着它)。(执行环境和做用域链销毁这一过程在图中没有体现出来)。匿名函数1
,(按照个人理解:此时匿名函数1的执行环境才会被压入环境栈中,同时生成 匿名函数1
的做用域链和活动对象),alert(i)
时,由于 匿名函数1
的活动对象中找不到 i
因此往做用域链的父级找,找到了 helper(1) 活动对象中的 i,因而 alert
了 0alert 1
,若是此时还不是很懂的话,能够回头再看看图,或者从 代码1 那里重新理解。若是有认真看的话,请思考一下个人见解和《JavaScript高级程序设计》的见解,到底哪一个是正确的,我仍是坚持本身的见解,若是有错的话,还请指出。闭包
因为闭包会携带包含它的函数的做用域,所以会比其余函数占用更多的内存。过分使用闭包可能会致使内存占用过多。模块化
来看一个闭包内引用着活动对象中的变量时,活动对象不被释放的例子,注意看图片右侧的 scope:
函数
从图中能够看出,param一、param3 所在的活动对象不在做用域链中,应该是被通知准备回收了或者已经回收了,而 param二、param4 所在的变量对象还在。
另外,若是闭包中有 eval
的话,因为不能判断 eval
里是否有引用父级做用域链活动对象中的变量,那么该做用域链中的全部活动对象都会被保留,因此在闭包中尽可能不要使用 eval
:
但若是在里面用的是 new Function("这里引用闭包外的变量")
这种写法,若是没有其它引用,父级做用域链的活动对象是不会保留的,下面这种写法最终会报错 Uncaught ReferenceError: param1 is not defined
:
若是闭包的做用域链中保存着一个HTML 元素,那么就意味着该元素将没法被销毁,代码以下,只要匿名函数存在,element 的引用数至少也是 1,所以它所占用的内存就永远不会被回收。(书中有讨论到这是IE9之前的问题,我怎么以为这是个广泛的问题呢,难道 Chrome 或其它浏览器中 element 的引用数至少还能是0吗?求不吝赐教)。
function assignHandler(){ var element = document.getElementById("someElement"); element.onclick = function(){ alert(element.id); }; }
function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ alert(id); }; element = null; }
闭包无处不在,若是真的要概括几个用处,以下:
咱们都知道,在 JavaScript 中,是不存在块级做用域的,也就是在 {} 里面声明的变量,在 {} 外面依然能够访问。但若是利用函数一旦执行完,其中执行环境和做用域链均销毁的特性,咱们能够这么作:
(function(){ // 让一个括号包着一个函数,至关于获得这个函数的引用, // 而后再在后面加个括号,执行这个函数,称这种函数为 当即执行函数 // 在这里面声明的变量,外部不可访问,除非 return 一个闭包或变量 // 若是此时外部是一个函数的话,那这个当即执行函数也是一个闭包 // 只是这个闭包并无返回些什么 })();
有了上面提到的模仿块级做用域,就能够减小全局变量的使用,jQuery 就是这么作的:
(function(window, undefined){ // ... // 这里实现 jQuery 的全部功能 // 势必会声明不少变量,若是暴露在全局做用域中会形成命名冲突 // 因此用一个当即执行函数包起来,可是为何要传入 window 呢 // 传入 window 是为了让 window 成为当前做用域下的变量,这样能够减小访问成本, // 另外便于压缩,好比将 window 压缩成 e,外部传进来的依然是 window // 传入 undefined 的缘由: // 在低版本的 IE 中,undefined 是可写的,有可能 undefined 就不是 undefined 了 window.jQuery = window.$ = jQuery; // 这里的 jQuery 就是一个函数,平时咱们用的时候是 $(参数、参数),因此,他也是个闭包嘛 // 上面是模块中的一种,而旦还有更高级的,jQuery 兼容 AMD 规范 if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define( "jquery", [], function () { // AMD 这里不详细介绍了,这里就是 define 一个模块 // 这里也是一个闭包 return jQuery; // 这里就是闭包中的闭包了 } ); } }(window))