在Vue
中有一個nextTick
方法,偶然一天,我發現無論代碼的順序如何,nextTick
老是要比setTimeout
先要執行。一樣是排隊,憑什麼你nextTick
就要比我快? javascript
開局一道題,內容全靠編。(在node下運行,答案在文末給出。)html
new Promise((resolve) => {
console.log(1);
process.nextTick(() => {
console.log(2);
});
resolve();
process.nextTick(() => {
console.log(3);
});
console.log(4);
}).then(() => {
console.log(5);
});
setTimeout(() => {
console.log(6);
}, 0);
console.log(7);
複製代碼
那麼,打印的順序究竟是什麼呢?java
for(var i = 0; i < 5; i++){
setTimeout(function after() {
console.log(i);
}, 0);
}
複製代碼
這道題想必你們都見得不少了,答案脫口而出5個5。爲何呢? 答:閉包。 爲何會產生閉包呢? 答:。。。node
這一切的一切都要從女媧補天開始提及(你咋不從盤古開天開始提及呢?)。segmentfault
簡單說明一下:api
因此,即使是setTimeout(fn, 0)
(實際上最小時間間隔是4ms)也是會從下一個事件週期開始執行。promise
上例中,因爲after函數
引用了i
而且會在下一個事件週期中被調用,致使了i
的內存沒辦法被釋放,等下個週期再來,哼 生米都煮成稀飯了。i
都被煮成5了。瀏覽器
關於內存,給你們推薦一篇我曾經翻譯的一篇文章JavaScript是如何工做的:內存管理 + 如何處理4個常見的內存泄漏。 對理解閉包也很是有幫助。session
這裏我只是簡單提了一下事件循環,更多的細節參考文末參考文獻。閉包
一個宿主環境只有一個事件循環,但能夠有多個任務隊列。宏任務隊列(macro task)與微任務隊列(micro task)就是其中之二。
每次事件循環的時候,會先執行宏任務隊列中的任務,而後再執行微任務隊列中的任務。那麼宏任務與微任務分別有哪些呢?
new Promise((resolve) => {
resolve();
}).then(() => {
console.log(1);
});
setTimeout(() => {
console.log(2);
}, 0);
console.log(3);
複製代碼
按照上面的說法,應該打印出 三、二、1啊。但實際上卻打印出了三、一、2。原來像process.nextTick和Promise這種微任務,都添加的當前循環的微任務隊列之中。因此會比當前循環中的全部宏任務要後執行,會比下個循環中的宏任務要先執行。
process.nextTick(() => {
console.log(1);
});
new Promise((resolve) => {
resolve();
}).then(() => {
console.log(2);
});
process.nextTick(() => {
console.log(3);
});
複製代碼
爲何我要把這兩個同屬於微任務的拎出來提一下呢?本身測試一下吧,由於結果大概會出乎你的意料。 why?
還好互聯網是強大的。沒有什麼是百度不到的,若是有,那就google。
「process.nextTick 永遠大於 promise.then,緣由其實很簡單。。。在Node中,_tickCallback在每一次執行完TaskQueue中的一個任務後被調用,而這個_tickCallback中實質上幹了兩件事:
Vue
中的nextTick
是宏任務與微任務混合使用,須要手動切換。終於真相大白了。定時器:好吧 我就原諒你比我先吧。
那麼開頭題的答案是什麼呢?仍是本身動手測試一下吧。
紙上得來終覺淺,覺知此事要躬行
咦,小姐?什麼小姐?你說的是
我:滾,打錯了而已。是小結。
我:什麼? 你請客!走啊走啊!
樓主被捕,完。
順序之爭還有一個奇怪的現象。
setImmediate(() => {
console.log(1);
});
setTimeout(() => {
console.log(2);
}, 0);
複製代碼
然而你會發現,特麼有時候打印一、2,有時候打印二、1。你爲何像個女人同樣啊。
nodejs官網給出的解釋是:
若是沒有在一個I/O週期執行,那麼其執行順序是不肯定的。
若是在一個I/O週期執行,setImmediate
老是優先於setTimeout
執行。
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
複製代碼
老是:先打印immediate再打印timeout。
參考文獻: