JavaScript 這門語言跟其他語言有區別,如果你看Jquery,Vue,React,Angular源碼的時候,如果你沒有理解事件機制很難理解裡面的一些內容。
JAVA ,Python是多線程語言(Python可是說是偽多線程)代碼的寫法跟JavaScript有太大的區別了,寫慣多線程語言的代碼,寫JavaScript要有一定的適應期。
現在HTML5,ES6,ES7,ES8,ES9有很大的改進很多時候不用寫這種回調的代碼了JavaScript有點像Python了現在。
先不要理什麼是任務,看一段代碼;
for(var i=0;i<5;i++ ){
setTimeout(function(){
console.log(i)
},0)
}
// 請問打印出是什麼
答案 5
js 是就是這麼奇葩,每個前端開發者都會被這個異步回調搞的頭暈腦脹。
下面這段代碼打印順序是什麼呢?
console.log('start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('end');
答案是:
- start
- end
- promise1
- promise2
- setTimeout
其實有些瀏覽器是不支持Promise 答案會有所不同。但是那些不是本篇文章考慮的內容。
為什麼會出現這樣的順序呢?
要想知道為啥會這樣那就要理解JavaScript的宏任務和微任務。
js執行的時候也就是每個線程(不知道沒有關係)都有一個事件隊列,這個事件隊列是等所有的主線程執行(原代碼的順序)完成後,再執行事件隊列執行的走的是事件循環,而且遵循事件循環。
這些宏任務源保證了在本任務源內的順序。但是瀏覽器每次都會選擇一個源中的一個宏任務去執行。這保證了瀏覽器給與一些宏任務(如用戶輸入)以更高的優先級。好的,跟著我繼續……
宏任務(task)
瀏覽器為了能夠使得JS內部task與DOM任務能夠有序的執行,會在一個task執行結束後,在下一個 task 執行開始前,對頁面進行重新渲染 (task->渲染->task->...)
在上面的例子中:
setTimeout的作用是等待給定的時間後為它的回調產生一個新的宏任務。
這就是為什麼打印‘setTimeout’在‘end’之後,即使setTimeout代碼再‘end’前面。因為打印‘end’是第一個宏任務裡面的事情,setTimeout它的回調產生一個新的宏任務,所以會在下一個事件循環的時候執行,即使是為0秒也是在‘end’之後執行promise1,promise2,在下一個
微任務(Microtasks )
微任務是在當前宏任務 task 執行結束後立即執行的任務。
需要異步的執行任務而又不需要分配一個新的宏任務 task。
這樣便可以減小一點性能的開銷。
只要執行棧中沒有其他的js代碼正在執行且每個宏任務執行完,微任務隊列會立即執行。
微任務包括了mutation observe的回調還有接下來的例子promise的回調。
知道了宏任務 (task)和微任務(Microtasks ),再來分析上面的代碼。
代碼執行的時候這個時候是第一個宏任務(task),執行 start ,setTimeout生成另外一個宏任務,也就代碼掛起來了等下個事件循環的時候執行。執行Promise的時候是一個微任務,所以是等到當前宏任務執行完在走,所以執行 end 再執行微任務promise1,promise2。這個時候第一個宏任務執行完畢,第二個宏任務開始執行setTimeout。
翻譯原文:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly
這篇文章希望能幫助到各位同學。
閱讀更多 閣主無名 的文章