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