一、創建閉包
創建閉包的常見方式,就是在一個函數內部創建另一個函數。
二、作用域鏈
當某個函數被調用的時候,會創建一個執行環境和相應的作用域鏈,然后使用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); }
奇怪的是,為什么這里我就理解了?