您好,登錄后才能下訂單哦!
老實說,寫這篇文章的時候心里是有點壓抑的,因為受到打擊了,為什么?就 因為喜歡折騰不小心看到了這個"簡單"的函數:
for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i) }, i * 1000); } console.log(i);
什么?這不就是我很久之前看到的先打印一個5,再打印一個5,之后每隔一秒就打印一個5,直到打印完6個5的實現方法嗎?那么問題來了,如果我要依次打印0,1,2,3,4,5的話我該怎么辦,其實在這之前我就知道有這兩個方法:一個是這樣:
function log(i){ setTimeout(function(){ console.log(i) },i*1000) }; for (var i = 0; i < 5; i++) { log(i) ; } console.log(i);
還有一個是這樣:
for(var i=0;i<5;i++){ (function(e){ setTimeout(function(){ console.log(e) },i*1000); })(i); }; console.log(i);
不怕笑話,在這之前我是沒搞懂這兩個函數真正意義上的作用是用來干嘛的,只強迫自己這樣記住這樣修改就可以了,但是現在不行啊,我有強迫癥啊!于是,我慢慢分析了一下,發現上面那段代碼可以分離成這樣:
i=0時;滿足條件;
setTimeout(function(){ console.log(i) },0*1000);
i=1時;滿足條件;
setTimeout(function(){ console.log(i) },1*1000);
i=2時;滿足條件;
setTimeout(function(){ console.log(i) },2*1000);
i=3時;滿足條件;
setTimeout(function(){ console.log(i) },3*1000);
i=4時;滿足條件;
setTimeout(function(){ console.log(i) },4*1000);
i=5時,不滿足條件,跳出循環,接著執行for循環后面的console.log(i),打印5;最后依次每秒打印5;
真有意思,為什么setTimeout里面的console.log會是后于for循環外面的console.log執行呢?直到我認識到了這個單詞=>"隊列", 隊列又有宏任務隊列(Macro Task)以及微任務隊列(Micro Task)之分 ,在javascript中:
macro-task包括:script(整體代碼), setTimeout , setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises , Object.observe, MutationObserver
上面函數的setTimeout就屬于宏任務
在js中,事件循環的順序是從script開始第一次循環,隨后全局上下文進入函數調用棧,碰到macro-task就將其交給處理它的模塊處理完之后將回調函數放進macro-task的隊列之中,碰到micro-task也是將其回調函數放進micro-task的隊列之中。直到函數調用棧清空只剩全局執行上下文,然后開始執行所有的micro-task。 當所有可執行的micro-task執行完畢之后。循環再次執行macro-task中的一個任務隊列 ,執行完之后再執行所有的micro-task,就這樣一直循環。
這就是為什么setTimeout里面的console.log會是后于for循環外面的console.log執行,在函數執行上下文中,seiTimeout函數會被放到處理他的macro-task的隊列之中,所以循環的時候setTimeout里面的function是不會被執行的,而是等到所有整體代碼(非隊列)跑完之后才會執行隊列中的函數;寫到這里,可能會有點懵逼,其實我也有點懵逼,哈哈哈!!
為了加深理解,還可以試試在里面加入Promise,于是就有了這個:
(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任務源先執行,全局上下文入棧。
2.script任務源的代碼在執行時遇到setTimeout,作為一個macro-task,將其回調函數放入自己的隊列之中。
3.script任務源的代碼在執行時遇到Promise實例。Promise構造函數中的第一個參數是在當前任務直接執行不會被放入隊列之中,因此此時輸出 1 。
4.在for循環里面遇到resolve函數,函數入棧執行之后出棧,此時Promise的狀態變成Fulfilled。代碼接著執行遇到console.log(2),輸出2。
5.接著執行,代碼遇到then方法,其回調函數作為micro-task入棧,進入Promise的任務隊列之中,此時Promise的then 里面的function回調函數跟setTimeout里面的function回調函數有著異曲同工之意,都會被放到各自的任務隊列中,
直到函數上下文即script中所有的非隊列代碼執行完畢后再執行,而且微任務隊列優先于宏任務隊列被處理,
總體順序為:上下文非隊列代碼>微任務隊列回調函數代碼>宏任務隊列回調函數代碼
6.代碼接著執行,此時遇到console.log(3),輸出3。
7.輸出3之后第一個宏任務script的代碼執行完畢,這時候開始開始執行所有在隊列之中的micro-task。then的回調函數入棧執行完畢之后出棧,這時候輸出5
8.這時候所有的micro-task執行完畢,第一輪循環結束。第二輪循環從setTimeout的任務隊列開始,setTimeout的回調函數入棧執行完畢之后出棧,此時輸出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') })
如果你的執行結果是:golb1=>glob1_promise=>glob1_then=>timeout1=>timeout1_promise=>timeout1_then=>prp_timeout=>time_timeout=>timeout1_timeout1,
可能異步隊列算是入門了吧!~~上面的代碼看起來有點雜亂,可能用asyns搭配await改造一下會更好,但是這或多或少是鄙人從setTimeout中得到的見解吧
總結
以上所述是小編給大家介紹的從setTimeout看js函數執行過程,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。