Closure經典例題解析、利用IIFE保存值
function buildFunctions() { var arr = []; //i=3時停止推f進陣列並return arr
for (var i = 0; i < 3; i++) {
arr.push(function () {
console.log(i)})} return arr; //返還陣列,此時i=3} var fs = buildFunctions();fs[0]() //3
fs[1]() //3
fs[2]() //3
簡單而言,
i=0 推了一個f1進陣列,arr=[f0];(沒執行f0函數!)
i=1 再推一個f2進陣列,arr=[f0, f1];(沒執行f1函數!)
i=2 再推一個f3進陣列,arr=[f0, f1, f2];(沒執行f2函數!)
i=3 此時 i 超過了迴圈的範圍條件,因此停止推陣列的動作。
會讓人誤以為console.log為1, 2, 3是因為以為推完一個陣列就會執行裡面的f,然而實際上是i跑完了並返還了陣列和裡面的f,呼叫後才開始執行。
而執行陣列中的f時裡面沒有i,要向外層尋找,此時雖然buildFunction已經從執行堆疊被移除,但記憶體中保存的i=3還存在,因為指向的記憶體相同,因此無論哪個函式執行i的結果都會是3。
若要console.log列印出1, 2, 3,就需要改變執行環境。若將push的函式加上IIFE,執行迴圈時就會當下執行IIFE並傳入當下的 i,並在IIFE的記憶體空間內保存為 j ,而裡頭的匿名函式在被呼叫時,就可以使用被保存的 j 值。
(詳細可以看下圖與註記)
function buildFunctions() { var arr = [];
for (var i = 0; i < 3; i++) {
arr.push( (function(j){
return function () {
console.log(j)}}
)(i) // 執行當下傳入的i值會被保留為j,裡面的匿名函式可以取用 )} return arr;}var fs = buildFunctions();fs[0]() //0
fs[1]() //1
fs[2]() //2