摘要:談?wù)勛约簩ο旅孢@道題目的理解問題這段代碼的輸出是三次,與預(yù)想的,,的輸出不符。此外,還可以使用下面這種方式這里可以使用閉包的知識進行解釋有關(guān)閉包的內(nèi)容可以參見文末的參考鏈接,也可以用作用域輔助理解。
問題談?wù)勛约簩ο旅孢@道題目的理解
for (var i = 1; i <= 3; i++) { setTimeout( function timer() { console.log(i); }, i * 1000 ); }
這段代碼的輸出是三次 4,與預(yù)想的 1,2,3 的輸出不符。以下解釋這一輸出的原因。
分析我們可以將 setTimeout 的第一個參數(shù) timer() 多帶帶寫出來,變成如下代碼:
for (var i = 1; i <= 3; i++) { function timer() { console.log(i); } setTimeout( timer, i * 1000 ); }
然后我們將循環(huán)展開,三次執(zhí)行過程的變化如下:
// 第一步: i = 1; setTimeout( timer, 1 * 1000 ); // 第二步:i = 2; setTimeout( timer, 2 * 1000 ); // 第三步 i = 3; setTimeout( timer, 3 * 1000 );
注意,在循環(huán)過程中,timer() 函數(shù)并未變化,也沒有執(zhí)行( 計時器還未開始 )。
由于 JavaScript 中使用 var i = xxx 聲明的變量是函數(shù)級別( 而非塊級 )的作用域,因而在 for 循環(huán)條件中聲明的 i 在 for 循環(huán)塊之外的最后一個函數(shù)體內(nèi)仍是可以訪問的,循環(huán)可以展開為:
var i = 4; function timer() { console.log(i); } setTimeout( timer, 1 * 1000 ); setTimeout( timer, 2 * 1000 ); setTimeout( timer, 3 * 1000 );
因而當(dāng)計時器開始的 1s, 2s, 3s 后,timer 會分別執(zhí)行,此時會輸出三次 4。
解決方法若要其每隔 1s 分別輸出 1, 2, 3,可以將 var i = 1 修改為 let i = 1,即:
for (let i = 1; i <= 3; i++) { function timer() { console.log(i); } setTimeout( timer, i * 1000 ); }
注意,由于 let 屬于 ES6 的語法,請注意測試使用的瀏覽器。
此時,由于 let i = xxx 為塊級別作用域,因而這一情況下的循環(huán)展開結(jié)果為:
{ let i = 1; setTimeout( timer, 1 * 1000 ); } { let i = 2; setTimeout( timer, 2 * 1000 ); } { let i = 3; setTimeout( timer, 3 * 1000 ); }
注意:這里的 {} 僅用來強調(diào)塊級別作用域。
此時便可以得到我們想要的輸出結(jié)果了。
此外,還可以使用下面這種方式:
for (var i = 1; i <= 3; i++) { (function(count){ setTimeout( function timer() { console.log(count); }, count * 1000 ); })(i) }
這里可以使用閉包的知識進行解釋( 有關(guān)閉包的內(nèi)容可以參見文末的參考鏈接 ),也可以用作用域輔助理解。
由于 var i = xxx 是函數(shù)級別作用域,這里通過一個立即函數(shù)將變量 i 傳入其中,使其包含在這一函數(shù)的作用域中。而在每次循環(huán)中,此立即函數(shù)都會將傳入的 i 值保存下來,因而其循環(huán)展開結(jié)果為:
(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 ); })()
自然也會得到我們想要的輸出結(jié)果。
擴展 - 塊級作用域和函數(shù)級作用域可以用以下代碼進行解釋:
{ 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); } // 按照函數(shù)內(nèi)的注釋輸出 test(); // 報錯:Uncaught ReferenceError: a is not defined console.log(a);
注:const 聲明的常量與 let 相同,也為塊級作用域。
參考for 循環(huán)中的...問題,為什么改 var 為 let 就可以解決? - segmentfault
ES6之let(理解閉包)和const命令 - 博客園
「每日一題」JS 中的閉包是什么? - 知乎專欄
前端基礎(chǔ)進階(四):詳細圖解作用域鏈與閉包 - 簡書
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/81514.html
摘要:同步異步回調(diào)傻傻分不清楚。分割線上面主要講了同步和回調(diào)執(zhí)行順序的問題,接著我就舉一個包含同步異步回調(diào)的例子。同步優(yōu)先回調(diào)內(nèi)部有個,第二個是一個回調(diào)回調(diào)墊底。異步也,輪到回調(diào)的孩子們回調(diào),出來執(zhí)行了。 同步、異步、回調(diào)?傻傻分不清楚。 大家注意了,教大家一道口訣: 同步優(yōu)先、異步靠邊、回調(diào)墊底(讀起來不順) 用公式表達就是: 同步 => 異步 => 回調(diào) 這口訣有什么用呢?用來對付面試的...
摘要:同步異步回調(diào)傻傻分不清楚。分割線上面主要講了同步和回調(diào)執(zhí)行順序的問題,接著我就舉一個包含同步異步回調(diào)的例子。同步優(yōu)先回調(diào)內(nèi)部有個,第二個是一個回調(diào)回調(diào)墊底。異步也,輪到回調(diào)的孩子們回調(diào),出來執(zhí)行了。 同步、異步、回調(diào)?傻傻分不清楚。 大家注意了,教大家一道口訣: 同步優(yōu)先、異步靠邊、回調(diào)墊底(讀起來不順) 用公式表達就是: 同步 => 異步 => 回調(diào) 這口訣有什么用呢?用來對付面試的...
摘要:異步函數(shù)是值通過事件循環(huán)異步執(zhí)行的函數(shù),它會通過一個隱式的返回其結(jié)果。 async 異步函數(shù) 不完全使用攻略 前言 現(xiàn)在已經(jīng)到 8012 年的尾聲了,前端各方面的技術(shù)發(fā)展也層出不窮,VueConf TO 2018 大會 也發(fā)布了 Vue 3.0的計劃。而在我們(我)的日常中也經(jīng)常用 Vue 來編寫一些項目。那么,就少不了 ES6 的登場了。那么話說回來,你真的會用 ES6 的 asyn...
摘要:結(jié)尾有關(guān)這四道經(jīng)典的指針筆試題講解就到此結(jié)束了,如果覺得文章對自己有所幫助,歡迎大家多多點贊收藏 ?前言 : 今天博主來講解4道經(jīng)典的指針筆試題,很多朋友沒有深刻理...
閱讀 4106·2023-04-26 01:48
閱讀 3274·2021-10-13 09:40
閱讀 1752·2021-09-26 09:55
閱讀 3644·2021-08-12 13:23
閱讀 1803·2021-07-25 21:37
閱讀 3438·2019-08-30 15:53
閱讀 1403·2019-08-29 14:16
閱讀 1404·2019-08-29 12:59