JS關(guān)于for循環(huán)中使用setTimeout的四種解決方案
概述
我們先來簡單了解一下setTimeout延時(shí)器的運(yùn)行機(jī)制。setTimeout會(huì)先將回調(diào)函數(shù)放到等待隊(duì)列中,等待區(qū)域內(nèi)其他主程序執(zhí)行完畢后,按時(shí)間順序先進(jìn)先出執(zhí)行回調(diào)函數(shù)。本質(zhì)上是作用域的問題。
因此若是這樣將不會(huì)得到想要的結(jié)果輸出1.2.3.4.5,而會(huì)連續(xù)輸出5個(gè)6。
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
這是因?yàn)閟etTimeout是異步執(zhí)行,每一次for循環(huán)的時(shí)候,setTimeout都執(zhí)行一次,但是里面的函數(shù)沒有被執(zhí)行,而是被放到了任務(wù)隊(duì)列里,等待執(zhí)行。只有主線上的任務(wù)執(zhí)行完,才會(huì)執(zhí)行任務(wù)隊(duì)列里的任務(wù)。也就是說它會(huì)等到for循環(huán)全部運(yùn)行完畢后,才會(huì)執(zhí)行fun函數(shù),但是當(dāng)for循環(huán)結(jié)束后此時(shí)i的值已經(jīng)變成了6,因此雖然定時(shí)器跑了5秒,控制臺(tái)上的內(nèi)容依然是6。
(注意:for循環(huán)從開始到結(jié)束的過程,需要維持幾微秒或幾毫秒,當(dāng)定時(shí)器跑完一秒之后for循環(huán)早已經(jīng)做完了。)
我們來看另一種情況:
for (var i=1; i<=5; i++) { (function() { setTimeout( function timer() { console.log( i ); }, i*1000 ); })(); }
由setTimeout的運(yùn)行機(jī)制可以知道,首先會(huì)運(yùn)行外部的所有主程序,雖然for循環(huán)內(nèi)形成了閉包,但是fun并沒有發(fā)現(xiàn)一個(gè)實(shí)參所以跟第一個(gè)例子并無實(shí)際差別,仍然是連續(xù)輸出5個(gè)6。
解決方案1:閉包
使用閉包是很經(jīng)典的一種做法:
for (var i=1; i<=5; i++) { (function(j) { setTimeout( function timer() { console.log( j ); }, j*1000 ); })(i); }
我們可以發(fā)現(xiàn)跟預(yù)期結(jié)果一致,依次輸出1到5,因是因?yàn)閷?shí)際參數(shù)跟定時(shí)器內(nèi)部的i有強(qiáng)依賴。
通過閉包,將i的變量駐留在內(nèi)存中,當(dāng)輸出j時(shí),引用的是外部函數(shù)的變量值i,i的值是根據(jù)循環(huán)來的,執(zhí)行setTimeout時(shí)已經(jīng)確定了里面的的輸出了。
解決方案2:拆分結(jié)構(gòu)
我們還可以將setTimeout的定義和調(diào)用分別放到不同部分:
function timer(i) { setTimeout( console.log( i ), i*1000 ); } for (var i=1; i<=5;i++) { timer(i); }
控制臺(tái)上輸出依然是依次輸出1到5。
解決方案3:let
這里再來說一說使用es6的let來解決此問題:
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
這個(gè)例子與第一個(gè)相比,只是把var更改成了let,可是控制臺(tái)的結(jié)果卻是依次輸出1到5。
因?yàn)閒or循環(huán)頭部的let不僅將i綁定到for循環(huán)中,事實(shí)上它將其重新綁定到循環(huán)體的每一次迭代中,確保上一次迭代結(jié)束的值重新被賦值。setTimeout里面的function()屬于一個(gè)新的域,通過var定義的變量是無法傳入到這個(gè)函數(shù)執(zhí)行域中的,通過使用let來聲明塊變量能作用于這個(gè)塊,所以function就能使用i這個(gè)變量了;這個(gè)匿名函數(shù)的參數(shù)作用域和for參數(shù)的作用域不一樣,是利用了這一點(diǎn)來完成的。這個(gè)匿名函數(shù)的作用域有點(diǎn)類似類的屬性,是可以被內(nèi)層方法使用的。
解決方案4:setTimeout第三個(gè)參數(shù)
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000, i ); }
由于每次傳入的參數(shù)是從for循環(huán)里面取到的值,所以會(huì)依次輸出1到5。
以上就是JS關(guān)于for循環(huán)中使用setTimeout的四種解決方案的詳細(xì)內(nèi)容,更多關(guān)于JS使用setTimeout的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Javascript學(xué)習(xí)筆記 delete運(yùn)算符
關(guān)于javascript的delete運(yùn)算符,MDN里有相關(guān)文檔。以下是我的學(xué)習(xí)筆記,更多是要關(guān)注特殊情況的使用和注意點(diǎn)。2011-09-09Bootstrap table學(xué)習(xí)筆記(2) 前后端分頁模糊查詢
這篇文章主要為大家分享了Bootstrap table學(xué)習(xí)筆記,前后端分頁模糊查詢,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05uniapp實(shí)現(xiàn)橫向滾動(dòng)選擇日期
這篇文章主要為大家詳細(xì)介紹了uniapp實(shí)現(xiàn)橫向滾動(dòng)選擇日期,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10BootStrap與validator 使用筆記(JAVA SpringMVC實(shí)現(xiàn))
這篇文章主要介紹了BootStrap與validator 使用筆記(JAVA SpringMVC實(shí)現(xiàn))的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09JavaScript觸發(fā)onScroll事件的函數(shù)節(jié)流詳解
這篇文章的內(nèi)容是說說最近在工作中遇到過的常見的問題。主要是關(guān)于JavaScript觸發(fā)onScroll事件的函數(shù)節(jié)流,文中由一個(gè)常見的問題開始展開,一步步的介紹解決的方法,有需要的朋友們下面來跟著小編一起看看吧。2016-12-12js實(shí)現(xiàn)跟隨鼠標(biāo)移動(dòng)且?guī)шP(guān)閉功能的圖片廣告實(shí)例
這篇文章主要介紹了js實(shí)現(xiàn)跟隨鼠標(biāo)移動(dòng)且?guī)шP(guān)閉功能的圖片廣告,實(shí)例分析了javascript操作鼠標(biāo)事件及html元素的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02微信小程序?qū)崿F(xiàn)簡單手寫簽名組件的方法實(shí)例
在使用微信的時(shí)候,為方便我們發(fā)送文件可以直接在上面進(jìn)行手寫簽名,這篇文章主要給大家介紹了關(guān)于利用微信小程序?qū)崿F(xiàn)簡單手寫簽名組件的相關(guān)資料,需要的朋友可以參考下2021-07-07動(dòng)態(tài)加載js、css的實(shí)例代碼
這篇文章主要介紹了動(dòng)態(tài)加載js、css的實(shí)例代碼的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-05-05