從setTimeout看js函數(shù)執(zhí)行過程
老實(shí)說,寫這篇文章的時(shí)候心里是有點(diǎn)壓抑的,因?yàn)槭艿酱驌袅?為什么?就 因?yàn)橄矚g折騰不小心看到了這個(gè)"簡單"的函數(shù):
for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i) }, i * 1000); } console.log(i);
什么?這不就是我很久之前看到的先打印一個(gè)5,再打印一個(gè)5,之后每隔一秒就打印一個(gè)5,直到打印完6個(gè)5的實(shí)現(xiàn)方法嗎?那么問題來了,如果我要依次打印0,1,2,3,4,5的話我該怎么辦,其實(shí)在這之前我就知道有這兩個(gè)方法:一個(gè)是這樣:
function log(i){ setTimeout(function(){ console.log(i) },i*1000) }; for (var i = 0; i < 5; i++) { log(i) ; } console.log(i);
還有一個(gè)是這樣:
for(var i=0;i<5;i++){ (function(e){ setTimeout(function(){ console.log(e) },i*1000); })(i); }; console.log(i);
不怕笑話,在這之前我是沒搞懂這兩個(gè)函數(shù)真正意義上的作用是用來干嘛的,只強(qiáng)迫自己這樣記住這樣修改就可以了,但是現(xiàn)在不行啊,我有強(qiáng)迫癥?。∮谑?,我慢慢分析了一下,發(fā)現(xiàn)上面那段代碼可以分離成這樣:
i=0時(shí);滿足條件;
setTimeout(function(){ console.log(i) },0*1000);
i=1時(shí);滿足條件;
setTimeout(function(){ console.log(i) },1*1000);
i=2時(shí);滿足條件;
setTimeout(function(){ console.log(i) },2*1000);
i=3時(shí);滿足條件;
setTimeout(function(){ console.log(i) },3*1000);
i=4時(shí);滿足條件;
setTimeout(function(){ console.log(i) },4*1000);
i=5時(shí),不滿足條件,跳出循環(huán),接著執(zhí)行for循環(huán)后面的console.log(i),打印5;最后依次每秒打印5;
真有意思,為什么setTimeout里面的console.log會(huì)是后于for循環(huán)外面的console.log執(zhí)行呢?直到我認(rèn)識(shí)到了這個(gè)單詞=>"隊(duì)列", 隊(duì)列又有宏任務(wù)隊(duì)列(Macro Task)以及微任務(wù)隊(duì)列(Micro Task)之分 ,在javascript中:
macro-task包括:script(整體代碼), setTimeout , setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises , Object.observe, MutationObserver
上面函數(shù)的setTimeout就屬于宏任務(wù)
在js中,事件循環(huán)的順序是從script開始第一次循環(huán),隨后全局上下文進(jìn)入函數(shù)調(diào)用棧,碰到macro-task就將其交給處理它的模塊處理完之后將回調(diào)函數(shù)放進(jìn)macro-task的隊(duì)列之中,碰到micro-task也是將其回調(diào)函數(shù)放進(jìn)micro-task的隊(duì)列之中。直到函數(shù)調(diào)用棧清空只剩全局執(zhí)行上下文,然后開始執(zhí)行所有的micro-task。 當(dāng)所有可執(zhí)行的micro-task執(zhí)行完畢之后。循環(huán)再次執(zhí)行macro-task中的一個(gè)任務(wù)隊(duì)列 ,執(zhí)行完之后再執(zhí)行所有的micro-task,就這樣一直循環(huán)。
這就是為什么setTimeout里面的console.log會(huì)是后于for循環(huán)外面的console.log執(zhí)行,在函數(shù)執(zhí)行上下文中,seiTimeout函數(shù)會(huì)被放到處理他的macro-task的隊(duì)列之中,所以循環(huán)的時(shí)候setTimeout里面的function是不會(huì)被執(zhí)行的,而是等到所有整體代碼(非隊(duì)列)跑完之后才會(huì)執(zhí)行隊(duì)列中的函數(shù);寫到這里,可能會(huì)有點(diǎn)懵逼,其實(shí)我也有點(diǎn)懵逼,哈哈哈?。?/p>
為了加深理解,還可以試試在里面加入Promise,于是就有了這個(gè):
(function copy() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })()
解釋一下=>
1.首先,script任務(wù)源先執(zhí)行,全局上下文入棧。
2.script任務(wù)源的代碼在執(zhí)行時(shí)遇到setTimeout,作為一個(gè)macro-task,將其回調(diào)函數(shù)放入自己的隊(duì)列之中。
3.script任務(wù)源的代碼在執(zhí)行時(shí)遇到Promise實(shí)例。Promise構(gòu)造函數(shù)中的第一個(gè)參數(shù)是在當(dāng)前任務(wù)直接執(zhí)行不會(huì)被放入隊(duì)列之中,因此此時(shí)輸出 1 。
4.在for循環(huán)里面遇到resolve函數(shù),函數(shù)入棧執(zhí)行之后出棧,此時(shí)Promise的狀態(tài)變成Fulfilled。代碼接著執(zhí)行遇到console.log(2),輸出2。
5.接著執(zhí)行,代碼遇到then方法,其回調(diào)函數(shù)作為micro-task入棧,進(jìn)入Promise的任務(wù)隊(duì)列之中,此時(shí)Promise的then 里面的function回調(diào)函數(shù)跟setTimeout里面的function回調(diào)函數(shù)有著異曲同工之意,都會(huì)被放到各自的任務(wù)隊(duì)列中,
直到函數(shù)上下文即script中所有的非隊(duì)列代碼執(zhí)行完畢后再執(zhí)行,而且微任務(wù)隊(duì)列優(yōu)先于宏任務(wù)隊(duì)列被處理,
總體順序?yàn)?上下文非隊(duì)列代碼>微任務(wù)隊(duì)列回調(diào)函數(shù)代碼>宏任務(wù)隊(duì)列回調(diào)函數(shù)代碼
6.代碼接著執(zhí)行,此時(shí)遇到console.log(3),輸出3。
7.輸出3之后第一個(gè)宏任務(wù)script的代碼執(zhí)行完畢,這時(shí)候開始開始執(zhí)行所有在隊(duì)列之中的micro-task。then的回調(diào)函數(shù)入棧執(zhí)行完畢之后出棧,這時(shí)候輸出5
8.這時(shí)候所有的micro-task執(zhí)行完畢,第一輪循環(huán)結(jié)束。第二輪循環(huán)從setTimeout的任務(wù)隊(duì)列開始,setTimeout的回調(diào)函數(shù)入棧執(zhí)行完畢之后出棧,此時(shí)輸出4。
最后,為了加深理解,再上一段代碼:
console.log('golb1'); setTimeout(function() { console.log('timeout1'); new Promise(function(resolve) { console.log('timeout1_promise'); resolve(); setTimeout(function(){ console.log('time_timeout') }); }).then(function() { console.log('timeout1_then') }) setTimeout(function() { console.log('timeout1_timeout1'); }); }) new Promise(function(resolve) { console.log('glob1_promise'); resolve(); setTimeout(function(){ console.log('prp_timeout') }); }).then(function() { console.log('glob1_then') })
如果你的執(zhí)行結(jié)果是:golb1=>glob1_promise=>glob1_then=>timeout1=>timeout1_promise=>timeout1_then=>prp_timeout=>time_timeout=>timeout1_timeout1,
可能異步隊(duì)列算是入門了吧!~~上面的代碼看起來有點(diǎn)雜亂,可能用asyns搭配await改造一下會(huì)更好,但是這或多或少是鄙人從setTimeout中得到的見解吧
總結(jié)
以上所述是小編給大家介紹的從setTimeout看js函數(shù)執(zhí)行過程,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- JavaScript使用setTimeout實(shí)現(xiàn)倒計(jì)時(shí)效果
- JavaScript setTimeout()基本用法有哪些
- JavaScript setInterval()與setTimeout()計(jì)時(shí)器
- 簡單通過settimeout看javascript的運(yùn)行機(jī)制
- 如何通過setTimeout理解JS運(yùn)行機(jī)制詳解
- js學(xué)使用setTimeout實(shí)現(xiàn)輪循動(dòng)畫
- JavaScript計(jì)時(shí)器用法分析【setTimeout和clearTimeout】
- 詳解JS中定時(shí)器setInterval和setTImeout的this指向問題
- JavaScript中setTimeout的那些事兒
- JS關(guān)于for循環(huán)中使用setTimeout的四種解決方案
相關(guān)文章
jsMind通過鼠標(biāo)拖拽的方式調(diào)整節(jié)點(diǎn)位置
這篇文章主要介紹了jsMind通過鼠標(biāo)拖拽的方式調(diào)整節(jié)點(diǎn)位置的方法,十分的簡單實(shí)用,推薦給有需要的小伙伴參考下。2015-04-04微信小程序?qū)崿F(xiàn)倒計(jì)時(shí)調(diào)用相機(jī)自動(dòng)拍照功能
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)倒計(jì)時(shí)調(diào)用相機(jī)自動(dòng)拍照功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06bootstrap配合Masonry插件實(shí)現(xiàn)瀑布式布局
這篇文章主要為大家詳細(xì)介紹了bootstrap配合Masonry插件實(shí)現(xiàn)瀑布式布局,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01javascript創(chuàng)建函數(shù)的20種方式匯總
這篇文章主要介紹了javascript創(chuàng)建函數(shù)的20種方式匯總的相關(guān)資料,需要的朋友可以參考下2015-06-06jQuery實(shí)現(xiàn)仿百度首頁滑動(dòng)伸縮展開的添加服務(wù)效果代碼
這篇文章主要介紹了jQuery實(shí)現(xiàn)仿百度首頁滑動(dòng)伸縮展開的添加服務(wù)效果代碼,通過jQuery相應(yīng)鼠標(biāo)事件控制頁面元素的動(dòng)態(tài)變換功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-09-09關(guān)于js中removeEventListener取消事件監(jiān)聽的坑
許多入前端不久的人都會(huì)遇到removeEventListener無法清除監(jiān)聽的情況,下面這篇文章主要給大家介紹了關(guān)于js中removeEventListener取消事件監(jiān)聽的坑,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09修改file按鈕的默認(rèn)樣式實(shí)現(xiàn)代碼
file按鈕作為上傳文件使用,不過默認(rèn)的樣式確實(shí)讓人不敢恭維啊,如何才可以修改為漂亮一點(diǎn)的呢?接下來與大家分享下具體的實(shí)現(xiàn)代碼,感興趣的朋友可以參考下哈2013-04-04