承接上一章:
接下來看另一個例子:
function buildArrayfun(){
var arr = [],i;
for(i =0;i<3;i++){
arr.push(
function(){console.log(i);}
);
}
return arr;
}
var b = buildArrayfun();
b[0]();
b[1]();
b[2]();
執行結果如下:
是3,3,3不是0,1,2,why?
同樣的,我們也來探究一下程式執行的過程:
1.首先,創造一個Global Execution context,並且抬升buildArrayfun與b
2.接著執行buildArrayfun,創造了execution context,並抬升變數i與arr,之後執行for迴圈,每次都會push一個function進入arr,反覆執行三次之後,i變數是3,這邊要注意一點,我跑迴圈的時候,是不會執行裡面的function(){console.log(i)}的,因為並沒有呼叫,也就是說,i不會帶入值,arr陣列裡面有三個長的一模一樣的function。
3.當buildArrayfun結束執行之後,會抽離execution conrext,但會留下變數i=3與arr[3]在記憶體之中
4.接著執行b[0]()會透過scope chain去找,最後找到i=3,所以console.log(3)印出3
5.之後b[0]()執行完畢,抽離execution context,同理b[1]()與b[2]()也是一樣的結果,這裡就不多贅述。
結論:
所以這邊我們得到一個結論,函數的執行與函數的宣告,其實是兩件事,函數執行的時候,就會透過closure這個現象,配合scope chain去找出對應的變數。所以這就是為什麼印出來是3,3,3而不是0,1,2。
如果要印出0,1,2這個結果,我們就必須使用IIFE(立即執行)去保留當下closure變數的結果,程式碼如下:
function buildArrayfun(){
var arr = [],i;
for(i =0;i<3;i++){
arr.push(
(function(j){
return function(){
console.log(j)
}
})(i)
);
}
return arr;
}
var b = buildArrayfun();
b[0]();
b[1]();
b[2]();
如此每次迴圈在跑的時候,都會去執行return function(){console.log(j))這行,讓之後j能找到當下迴圈執行closure所對應的變數i。
題外話,如果你想看function execution context 中的closure,找出變數對應的值,可以在執行該function之後進入debugger模式,F12會有顯示。
參考資料
https://www.udemy.com/javascriptjs/learn/v4/overview