function a(){ var i=0; function b(){alert(++i); } return b;}var c = a();c();这段代码有两个特点://声明函数function test(){ var str = "hello world"; console.log(str);}//调用函数test();在调用函数的时候会在内存中生成如下图的结构:
但是闭包的情况就有点特殊了,由于闭包函数可以访问外层函数中的变量,所以外层函数在执行结束后,其作用域活动对象并不会被释放(注意,外层函数执行结束后执行环境和对应的作用域链就会被销毁),而是被闭包函数的作用域链所引用,直到闭包函数被销毁后,外层函数的作用域活动对象才会被销毁。这也正是闭包要占用内存的原因。
所以使用闭包有好处,也有坏处,滥用闭包会造成内存的大量消耗。
使用闭包还有其他的副作用,可以说是bug,也可以说不是,相对不同的业务可能就会有不同的看法。
这个副作用是闭包函数只能取到外层函数变量的最终值。
测试代码如下:(这里使用了jquery对象)
/*闭包缺陷*/ (function($){var result = new Array(),i = 0;for(;i<10;i++){ result[i] = function(){return i; };}$.RES1 = result; })(jQuery); // 执行数组中的函数 $.RES1[0]();上面的代码先通过匿名函数表达式开辟了一块私有作用域,这个匿名函数就是我们上面所说的外层函数,该外层函数有一个参数$,同时还定义了变量result和 I , 通过for循环给数组result赋值一个匿名函数,这个匿名函数就是闭包,他访问了外层函数的变量I , 理论上数组resulti 会返回相应的数组下标值,实际情况却不如所愿。
那么这个副作用有没有办法可以修复呢?当然可以!
我们可以通过下面的代码来达到我们的预期。
/*修复闭包缺陷*/ (function($){var result = new Array(),i = 0;for(;i<10;i++){ result[i] = function(num){return function(){ return num;} }(i);}$.RES2 = result; })(jQuery); //调用闭包函数 console.log($.RES2[0]());上面的代码又在内存中发生了什么?我们同样用下面的一幅图来详细解释。看懂了上面的图,我们也就不难理解下面的图。
6.简单的例子
首先从一个经典错误谈起,页面上有若干个div, 我们想给它们绑定一个onclick方法,于是有了下面的代码
<div id="divTest"> <span>0</span> <span>1</span> <span>2</span> <span>3</span></div><div id="divTest2"> <span>0</span> <span>1</span> <span>2</span> <span>3</span></div>$(document).ready(function() {var spans = $("#divTest span");for (var i = 0; i < spans.length; i++) { spans[i].onclick = function() {alert(i); }}});很简单的功能可是却偏偏出错了,每次alert出的值都是4,简单的修改就好使了var spans2 = $("#divTest2 span");$(document).ready(function() { for (var i = 0; i < spans2.length; i++) {(function(num) { spans2[i].onclick = function() {alert(num); }})(i); }});7.内部函数function outerFn () { functioninnerFn () {}}innerFn就是一个被包在outerFn作用域中的内部函数。这意味着,在outerFn内部调用innerFn是有效的,而在outerFn外部调用innerFn则是无效的。下面代码会导致一个JavaScript错误:function outerFn() { document.write("Outer function<br/>"); function innerFn() {document.write("Inner function<br/>"); }}innerFn();//Uncaught ReferenceError: innerFn is not defined不过在outerFn内部调用innerFn,则可以成功运行:function outerFn() { document.write("Outer function<br/>"); function innerFn() {document.write("Inner function<br/>"); } innerFn();}outerFn();8、伟大的逃脱(内部函数如何逃脱外部函数)//定义全局变量逃脱var globalVar;function outerFn() { document.write("Outer function<br/>");function innerFn() {document.write("Inner function<br/>"); } globalVar = innerFn;}outerFn(); //Outer function Inner functionglobalVar(); //Outer function Inner functioninnerFn(); //ReferenceError: innerFn is not defined调用outerFn时会修改全局变量globalVar,这时候它的引用变为innerFn,此后调用globalVar和调用innerFn一样。这时在outerFn外部直接调用innerFn仍然会导致错误,这是因为内部函数虽然通过把引用保存在全局变量中实现了逃脱,但这个函数的名字依然只存在于outerFn的作用域中。function outerFn() { document.write("Outer function<br/>"); function innerFn() {document.write("Inner function<br/>"); } return innerFn;}var fnRef = outerFn();fnRef();这里并没有在outerFn内部修改全局变量,而是从outerFn中返回了一个对innerFn的引用。通过调用outerFn能够获得这个引用,而且这个引用可以可以保存在变量中。function outerFn() { document.write("Outer function<br/>"); function innerFn() {var innerVar = 0; innerVar++; document.write("Inner function "); document.write("innerVar = "+innerVar+"<br/>");}return innerFn;}var fnRef = outerFn();fnRef();fnRef();var fnRef2 = outerFn();fnRef2();fnRef2();每当通过引用或其它方式调用这个内部函数时,就会创建一个新的innerVar变量,然后加1,最后显示Outer functionInner function innerVar = 1Inner function innerVar = 1Outer functionInner function innerVar = 1Inner function innerVar = 1内部函数也可以像其他函数一样引用全局变量:
var globalVar = 0;function outerFn() { document.write("Outer function<br/>"); function innerFn() {globalVar++;document.write("Inner function ");document.write("globalVar = " + globalVar + "<br/>"); } return innerFn;} var fnRef = outerFn(); fnRef(); fnRef(); var fnRef2 = outerFn(); fnRef2(); fnRef2();现在每次调用内部函数都会持续地递增这个全局变量的值:Outer functionInner function globalVar = 1Inner function globalVar = 2Outer functionInner function globalVar = 3Inner function globalVar = 4但是如果这个变量是父函数的局部变量又会怎样呢?因为内部函数会引用到父函数的作用域(有兴趣可以了解一下作用域链和活动对象的知识),内部函数也可以引用到这些变量
function outerFn() { var outerVar = 0; document.write("Outer function<br/>"); function innerFn() {outerVar++;document.write("Inner function ");document.write("outerVar = " + outerVar + "<br/>"); } return innerFn;}var fnRef = outerFn();fnRef();fnRef();var fnRef2 = outerFn();fnRef2();fnRef2();这一次结果非常有意思,也许或出乎我们的意料Outer functionInner function outerVar = 1Inner function outerVar = 2Outer functionInner function outerVar = 1Inner function outerVar = 2我们看到的是前面两种情况合成的效果,通过每个引用调用innerFn都会独立的递增outerVar。也就是说第二次调用outerFn没有继续沿用outerVar的值,而是在第二次函数调用的作用域创建并绑定了一个一个新的outerVar实例,两个计数器完全无关。
function outerFn() {var outerVar = 0;document.write("Outer function<br/>");function innerFn1() { outerVar++; document.write("Inner function 1 "); document.write("outerVar = " + outerVar + "<br/>");}function innerFn2() { outerVar += 2; document.write("Inner function 2 "); document.write("outerVar = " + outerVar + "<br/>");}return { "fn1": innerFn1, "fn2": innerFn2 };}var fnRef = outerFn();fnRef.fn1();fnRef.fn2();fnRef.fn1();var fnRef2 = outerFn();fnRef2.fn1();fnRef2.fn2();fnRef2.fn1();我们映射返回两个内部函数的引用,可以通过返回的引用调用任一个内部函数,结果:Outer functionInner function 1 outerVar = 1Inner function 2 outerVar = 3Inner function 1 outerVar = 4Outer functionInner function 1 outerVar = 1Inner function 2 outerVar = 3Inner function 1 outerVar = 4innerFn1和innerFn2引用了同一个局部变量,因此他们共享一个封闭环境。当innerFn1为outerVar递增一时,久违innerFn2设置了outerVar的新的起点值,反之亦然。我们也看到对outerFn的后续调用还会创建这些闭包的新实例,同时也会创建新的封闭环境,本质上是创建了一个新对象,自由变量就是这个对象的实例变量,而闭包就是这个对象的实例方法,而且这些变量也是私有的,因为不能在封装它们的作用域外部直接引用这些变量,从而确保了了面向对象数据的专有性。
for (var i = 0; i < spans.length; i++) { spans[i].onclick = function() {alert(i); }}上面代码在页面加载后就会执行,当i的值为4的时候,判断条件不成立,for循环执行完毕,但是因为每个span的onclick方法这时候为内部函数,所以i被闭包引用(闭包引用传的是引用),内存不能被销毁,i的值会一直保持4,直到程序改变它或者所有的onclick函数销毁(主动把函数赋为null或者页面卸载)时才会被回收。这样每次我们点击span的时候,onclick函数会查找i的值(作用域链是引用方式),一查等于4,然后就alert给我们了。