談談本身對下面這道題目的理解html
for (var i = 1; i <= 3; i++) { setTimeout( function timer() { console.log(i); }, i * 1000 ); }
這段代碼的輸出是三次 4,與預想的 1,2,3 的輸出不符。如下解釋這一輸出的緣由。前端
咱們能夠將 setTimeout
的第一個參數 timer()
單獨寫出來,變成以下代碼:segmentfault
for (var i = 1; i <= 3; i++) { function timer() { console.log(i); } setTimeout( timer, i * 1000 ); }
而後咱們將循環展開,三次執行過程的變化以下:瀏覽器
// 第一步: i = 1; setTimeout( timer, 1 * 1000 ); // 第二步:i = 2; setTimeout( timer, 2 * 1000 ); // 第三步 i = 3; setTimeout( timer, 3 * 1000 );
注意,在循環過程當中,timer()
函數並未變化,也沒有執行( 計時器還未開始 )。閉包
因爲 JavaScript 中使用 var i = xxx
聲明的變量是函數級別( 而非塊級 )的做用域,於是在 for 循環條件中聲明的 i
在 for 循環塊以外的最後一個函數體內還是能夠訪問的,循環能夠展開爲:函數
var i = 4; function timer() { console.log(i); } setTimeout( timer, 1 * 1000 ); setTimeout( timer, 2 * 1000 ); setTimeout( timer, 3 * 1000 );
於是當計時器開始的 1s, 2s, 3s 後,timer 會分別執行,此時會輸出三次 4。測試
若要其每隔 1s 分別輸出 1, 2, 3,能夠將 var i = 1
修改成 let i = 1
,即:code
for (let i = 1; i <= 3; i++) { function timer() { console.log(i); } setTimeout( timer, i * 1000 ); }
注意,因爲 let
屬於 ES6 的語法,請注意測試使用的瀏覽器。htm
此時,因爲 let i = xxx
爲塊級別做用域,於是這一狀況下的循環展開結果爲:blog
{ let i = 1; setTimeout( timer, 1 * 1000 ); } { let i = 2; setTimeout( timer, 2 * 1000 ); } { let i = 3; setTimeout( timer, 3 * 1000 ); }
注意:這裏的 {}
僅用來強調塊級別做用域。
此時即可以獲得咱們想要的輸出結果了。
此外,還能夠使用下面這種方式:
for (var i = 1; i <= 3; i++) { (function(count){ setTimeout( function timer() { console.log(count); }, count * 1000 ); })(i) }
這裏能夠使用閉包的知識進行解釋( 有關閉包的內容能夠參見文末的參考連接 ),也能夠用做用域輔助理解。
因爲 var i = xxx
是函數級別做用域,這裏經過一個當即函數將變量 i
傳入其中,使其包含在這一函數的做用域中。而在每次循環中,此當即函數都會將傳入的 i
值保存下來,於是其循環展開結果爲:
(function(){ var count = 1; setTimeout( function timer() { console.log(count); }, count * 1000 ); })() (function(){ var count = 2; setTimeout( function timer() { console.log(count); }, count * 1000 ); })() (function(){ var count = 3; setTimeout( function timer() { console.log(count); }, count * 1000 ); })()
天然也會獲得咱們想要的輸出結果。
能夠用如下代碼進行解釋:
{ let i = 2; // 輸出 2 console.log(i); } // 報錯:Uncaught ReferenceError: i is not defined console.log(i);
function test(){ // 因爲變量提高,輸出 undefined console.log(a); { var a = 1; } // 輸出 1 console.log(a); } // 按照函數內的註釋輸出 test(); // 報錯:Uncaught ReferenceError: a is not defined console.log(a);
注:const
聲明的常量與 let
相同,也爲塊級做用域。