《你不知道的JS》讀書筆記之閉包在循環中的應用

閉包在循環中的應用

延遲函數的回調會在循環結束時才執行;
事實上,當定時器運行時即便沒給迭代中執行的是 setTime(..., 0),多有的回調函數依然是在循環結束後纔會被執行,所以會每次輸出一個6出來。
for(var i=1; i<=5; i++){
    setTimeout( function timer(){
        let temp = new Date();
        console.log(i + "----" + temp.toLocaleTimeString() + "." + temp.getMilliseconds());
    }, i*1000);
    
    if(i == 5){
        var now = new Date();
        console.log("for循環結束----"+now.toLocaleTimeString() + "." + now.getMilliseconds());
    }
}

// for循環結束----下午7:51:29.885
// 6----下午7:51:30.885
// 6----下午7:51:31.885
// 6----下午7:51:32.885
// 6----下午7:51:33.885
// 6----下午7:51:34.885

利用當即執行函數建立做用域,但做用域爲空(沒有傳參),並未奏效;

for (var i = 0; i <= 5; i++){
    (function(){
        setTimeout(function timer(){
           let temp = new Date();
           console.log(i + "----" + temp.toLocaleTimeString() + "." + temp.getMilliseconds());
        }, i*1000)
    })();
    
    if(i == 5){
        var now = new Date();
        console.log("for循環結束----"+now.toLocaleTimeString() + "." + now.getMilliseconds());
    }
}

在當即執行函數中捕獲一個變量

for (var i = 0; i <= 5; i++){
    (function(){
        var j = i; // IIFE有了本身的變量
        setTimeout(function timer(){
            let temp = new Date();
            console.log(j + "----" + temp.toLocaleTimeString() + "." + temp.getMilliseconds());
        }, j*1000)
    })();
    
    if(i == 5){
        var now = new Date();
        console.log("for循環結束----"+now.toLocaleTimeString() + "." + now.getMilliseconds());
    }
}
// for循環結束----下午8:14:28.915
// 0----下午8:14:28.916
// 1----下午8:14:29.916
// 2----下午8:14:30.916
// 3----下午8:14:31.916
// 4----下午8:14:32.916
// 5----下午8:14:33.916

改進

1. 利用當即執行函數(IIFE)傳參

利用當即執行函數爲每一個迭代都生成一個新的做用域,使得延遲函數的回調能夠將新的做用域封閉在每一個迭代內部;
for (var i = 0; i <= 5; i++){
    (function(j){
        setTimeout(function timer(){
            let temp = new Date();
            console.log(j + "----" + temp.toLocaleTimeString() + "." + temp.getMilliseconds());
        }, j*1000)
    })(i);
}

2. 利用setTimeout給回調函數(callback)中傳參,產生timerfor循環做用域的閉包

利用延遲函數向其回調函數中傳參,爲每一個迭代中 callback中生成新的做用域,使得延遲函數的回調能夠將新的做用域封閉在每一個迭代內部;
for (var i = 0; i <= 5; i++){
    setTimeout(function timer(j){
        let temp = new Date();
        console.log(j + "----" + temp.toLocaleTimeString() + "." + temp.getMilliseconds());
    }, i*1000, i);
}

3. 利用let聲明劫持塊做用域

本質:將一個塊轉換成了一個能夠被關閉的做用域。
for(var i=0; i<5; i++){
    let j = i;// 閉包的塊做用域
    setTimeout(function timer(){
        console.log(j);
    }, j*1000);
}

4. 利用for循環頭部的let聲明

for循環頭部的 let聲明的特殊行爲:使得變量 i在循環過程當中不止被聲明一次, 每次迭代都會聲明
隨後的每一個迭代都會使用上一個迭代結束時的值來初始化這個變量。
for(let i=0; i<5; i++){
    setTimeout(function timer(){
        console.log(i);
    }, i*1000);
}
相關文章
相關標籤/搜索