閱讀570 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Javascript的setTimeout()使用閉包特性時需要注意的問題

function createFunction1(){
    for(var i=0;i<5;i++){
        function s(){
            console.log(i);
        }
        s();
    }
}
createFunction1();   //0 1 2 3 4;

這是一個正常的函數

function createFunction2(){
    for(var i=0;i<5;i++){
        setTimeout(function timer(){
            console.log(i);
        },i*1000);
    }
}
createFunction2();  //每隔1秒輸出‘5’、共輸出5次

並不會按照我們預想的每隔1秒分別輸出0、1、2、3、4

原因:此函數在for循環的第一層是setTimeout函數,他的執行和createFunction1中的s函數一樣,將按分別在1秒後、2秒後、3秒後執行。但這兒需要注意的是,setTimeout的內部函數timer並沒有立即執行,for循環中的i將會把值分別賦給setTimeout外部參數中的i,但其內部函數timer()則隻會引用包含函數setTimeout()中的變量的最後一個值。因為閉包所保存的是整個變量對象,而不是某個特殊的變量。當然其中的這些處理變化,都是瞬間完成的,與執行時間並無關係,即使把1000改成0效果還是一樣的。

重寫一下這個函數:

function createFunction3(){
    for (var i=0;i<5;i++){
        (function(i){
            setTimeout(function timer(){
                console.log(i);
            },i*1000);
        })(i);
    }
}
createFunction3();   //每隔1秒分別輸出0 1 2 3 4

這個例子,給外部包裝了一個立即執行的匿名函數,setTimeout裏麵的匿名函數不再引用外部函數的參數,而是直接引用外部匿名函數的參數,這時,一切就會按照我們預想的來執行了

另外ES6為我們提供了另一種解決方案:

for (let i=1; i<5; i++) {
    setTimeout( function timer(){
        console.log( i );
    }, 1000 );
}   //每隔1秒分別輸出0 1 2 3 4

這兩種方法都不是解決異步問題的,而是解決變量作用域的問題的。因為函數 timer() 屬於一個新的域,通過 var 定義的變量是無法傳入到這個函數執行域中的,於是第一種是通過傳入參數,間接的把變量傳入到 timer 中;第二種是通過使用 let 來聲明塊變量,這時候變量就能作用於這個塊,所以 timer 就能使用 i 這個變量了

setTimeout經常被用於延遲執行某個函數

setTimeout(function(){}, timeout);

有時為了進行異步處理,而使用setTimeout(function…,0);

function f(){
    // get ready
    setTimeout(function(){
        // do something
    }, 0);  
    return …;
}

在setTimeout設定的函數處理器之前,函數f返回;
在使用異步處理時,尤其是使用閉包特性時,要特別小心

for(var i = 0 ; i < 10; i++){
    setTimeout(function(){
        console.log(i);
    }, 0);
}

對於初次使用這種方式的同學來說,很可能會認為程序會打印0…9,可結果確實打印10個10;問題就在於,當循環完成時,function得到執行,而i已經變成10,console.log(i)中使用的是10!

那麼可以換一種方式,用函數參數來保存0….9(其實也是利用了閉包):

for(var i = 0 ; i < 10; i++){
    setTimeout((function(i){
        return function(){
            console.log(i);
        }
    })(i), 0);
}

另外再來看一個例子:

function test() {
    for (var i = 0; i < 10; i++) {
        setTimeout(function() {
            console.log(i)
        }, 1000);
    }
}
test();  //9 9 9 9 9 9 9 9 9 9 

把他修改一下:

function test() {
    for (var i = 0; i < 10; i++) {
        setTimeout(console.log(i), 1000);
    }
}
test();  //1 2 3 4 5 6 7 8 9

來看一道考察JavaScript運行機製的題目

setTimeout(function() {
    console.log(1)
}, 0);
new Promise(function executor(resolve) {
    console.log(2);
    for( var i=0 ; i<10000 ; i++ ) {
        i == 9999 && resolve();  
    }
    console.log(3);
}).then(function() {
    console.log(4);
});
console.log(5); //2 3 5 4 1

這道題考察JavaScript的運行機製。首先先碰到一個 setTimeout,於是會先設置一個定時,在定時結束後將傳遞這個函數放到任務隊列裏麵,因此開始肯定不會輸出 1 。然後是一個 Promise,裏麵的函數是直接執行的,因此應該直接輸出 2 3 。然後,Promise 的 then 應當會放到當前 tick 的最後,但是還是在當前 tick 中。因此,應當先輸出 5,然後再輸出 4 。最後在到下一個 tick,就是 1

最後更新:2017-06-23 23:03:01

  上一篇:go  《深入理解Elasticsearch(原書第2版)》一2.5.1 查詢方式分類
  下一篇:go  《深入理解Elasticsearch(原書第2版)》一2.4.4 後置過濾和過濾查詢