一、创建闭包
创建闭包的常见方式,就是在一个函数内部创建另一个函数。
二、作用域链
当某个函数被调用的时候,会创建一个执行环境和相应的作用域链,然后使用arguments初始化对象。这个对象叫做活动对象。
在作用域链中,外部函数的活动对象始终处于第二位。以此类推,直到作用域链终点——全局执行环境。 首先,让我先来看看什么叫做活动对象。
function compare(a,b){ if(a>b){ return true; }else if(a<b){ return false; }else{ return 0; } } var result=compare(5,10);//false console.log(result);
这段代码中,首先定义了compare()函数,然后在全局作用域中调用了它。在调用这个函数的时候,创建了arguments、a、b这三个活动对象。他们处在作用域链的第一位。
而result、compare被称为变量对象,他们处于全局执行环境下,在作用域链中处于第二位。
全局环境的变量对象始终存在,在函数中访问一个变量时,就会在作用域链中寻找具有相应名字的变量。函数执行完毕,活动对象就被销毁。而闭包的特殊就在于此,活动对象没有被销毁!
看这段代码:
function compare(propertyValue){ return function(obj1,obj2){ var a=obj1; var b=obj2; if(a<b){ return true; }else if(a>b){ return false; }else{ return 0; } } } var fun=compare("value"); console.log(typeof fun);//function var result=fun(5,10); console.log(result);//true
函数compare()中包含了一个匿名函数,那么,该匿名函数,也就是闭包的作用域链中,就会有compare()函数的活动对象。因此,该闭包作用域链其实有三节:
第一节,闭包的活动对象,obj1,obj2 和arguments;
第二节,compare()函数的活动对象,arguments和propertyValue;
第三节,全局变量对象,compare。
这意味着什么意思呢?就是在compare()这个函数在执行完毕以后,他的活动对象propertyValue和arguments也不会被销毁。因为匿名函数的作用域链仍然在引用这个活动对象。也就是说,compare()执行完以后,其执行环境的作用域链会被销毁,但是他的活动对象却不会被销毁。除非匿名函数被销毁。
而在js中,内置的工具函数setTimeout,往往就会造就一个闭包。
function wait(message){ setTimeout(function timer(){ console.log(message); },1000); } wait('5');//5
timer()函数就是一个闭包,即使wait()执行1000毫秒后,内部作用域仍然不会消失。timer具有wait()作用域的闭包。
总结:由于闭包会携带外部函数的作用域,所以它会占用更多的内存。建议不要过多使用闭包,会导致内存占用过多。
三、闭包与变量
function fun(){ var result=new Array(); for(var i=0;i<4;i++){ result[i]=function(){ return i; }; } return result; } var a=fun(); console.log(a);//Array(4) [, , , ]
好吧,这段代码我还是看的半懂不懂。似乎理解他为什么会返回这个,似乎又不理解。而且和书上写的也不一样啊。也没返回4个“3”啊!这是为啥内?
for(var i=0;i<2;i++){ setTimeout(function timer(){ console.log(i); },i*1000); } console.log('daoda')//daoda;2;2
看到没有,竟然先输出了"daoda",然后又输出了两个“2”!
首先解释“2”是怎么来的。首先,这个循环终止的条件是i不再小于2,也就是说,条件首次成立时,i的值为2。因此,输出显示的是循环结束时i的值。
延迟函数的回调是在循环结束的时候才执行。即使执行的是setTimeout(...,0),所有的回调函数依然是在循环结束后才会执行。因此每次输出的都是2!
你以为每次迭代都会有对应的i,但是根据作用域的原理,尽管循环中的5个函数是在各个迭代部分中分别定义的,但是它们却都被封闭在一个共享的全局作用域中,因此实际上只有1个i。
for(var i=0;i<2;i++){ (function(){ setTimeout(function timer(){ console.log(i); },i*1000); })(); } console.log('蓝色橙汁');//"蓝色橙汁";2;2
IIFE会立即执行函数,为什么输出的还是两个“2”呢?
这是因为IIFE的作用域是空的。他要有自己的变量,用来在每次迭代中存储i的值才行。
代码写成这样才可以:
for(var i=1;i<=5;i++){ (function(){ var j=i; setTimeout(function timer(){ console.log(j);//1,2,3,4,5!这样就可以了 },j*1000); })(); }
上面这段代码可以做一些改进:
for(var i=1;i<=5;i++){ (function(j){ setTimeout(function timer(){ console.log(j);//1,2,3,4,5!这样就可以了 },j*1000); })(i); }
奇怪的是,为什么这里我就理解了?