歡迎你們關注個人 githubhtml
是由JS的用途決定的,其用途主要是與用戶互動,以及操做DOM,若是不是單線程的話會帶來很複雜的同步問題。好比:若是是多線程的話,一個用戶新建一個DOM,另外一個用戶刪除同一個DOM,該如何處理將變得很是麻煩。html5
在html5中提出了web worker(todo 改天添加web worker的講解)標準,JS能夠建立多個線程或者是iframe,可是其餘子線程主要是用來進行JS計算不能操做DOM,且受到子線程的控制。因此也並無改變JS單線程的本質git
因爲是單線程,全部任務須要排隊,可是若是隊列中全部的任務都是同步的話會形成資源的浪費。github
因而任務分爲兩類:同步任務和異步任務。web
異步任務的過程:segmentfault
主線程(函數調用棧)發起一個異步請求,相應的工做線程接收請求,並告知主線程已經收到,主線程繼續執行後面的同步代碼,同時工做線程執行異步任務,工做線程完成工做後,通知主線程,主線程收到通知後,執行回調函數
複製代碼
宏任務(macro-task)主要是:script代碼段、setTimeout、setInterval、Promise的構造函數是同步的、setImmediate、I/O、UIrenderingpromise
微任務(micro-task)主要是:Promise的回調、process.nextTick多線程
瞭解任務隊列以前,先了解一下任務源,咱們將發起異步任務的稱之爲任務源(setTimeout、Promise等),進入任務隊列的是他們指定的任務。異步
在一個線程中,事件循環是惟一的,任務隊列是多個的。來自不一樣任務源的隊列進入到不一樣的任務隊列,setTimeout和setInterval是同源的函數
事件循環的步驟:
一、運行主線程(函數調用棧)中的同步任務
二、主線程(函數調用棧)執行到任務源時,通知相應的webAPIs進行相應的執行異步任務,將任務源指定的異步任務放入任務隊列中
三、主線程(函數調用棧)中的任務執行完畢後,而後執行全部的微任務,再執行宏任務,找到一個任務隊列執行完畢,再執行全部的微任務
四、不斷執行第三步
複製代碼
事件循環:指主線程重複從任務隊列中取消息,執行的過程
先來一個簡單的例子:
setTimeout(() => {
console.log('begin')
})
new Promise((resolve) => {
console.log('promise begin')
for(let i = 0; i < 1000; i++) {
i == 999 && resolve()
}
}).then(() => {
console.log('then begin')
})
console.log('end')
複製代碼
由於promise的構造函數是同步的,promise.then是異步的微任務,因此promise beigin先於end
根據上面對宏任務和微任務的分析,其輸出的狀況爲【promise begin——end——then begin——begin】
再來一個複雜點的,咱們來一步一步的分析一個例子來看:
console.log('golb1');
setTimeout(function() {
console.log('timeout1');
process.nextTick(function() {
console.log('timeout1_nextTick');
})
new Promise(function(resolve) {
console.log('timeout1_promise');
resolve();
}).then(function() {
console.log('timeout1_then')
})
})
setImmediate(function() {
console.log('immediate1');
process.nextTick(function() {
console.log('immediate1_nextTick');
})
new Promise(function(resolve) {
console.log('immediate1_promise');
resolve();
}).then(function() {
console.log('immediate1_then')
})
})
process.nextTick(function() {
console.log('glob1_nextTick');
})
new Promise(function(resolve) {
console.log('glob1_promise');
resolve();
}).then(function() {
console.log('glob1_then')
})
setTimeout(function() {
console.log('timeout2');
process.nextTick(function() {
console.log('timeout2_nextTick');
})
new Promise(function(resolve) {
console.log('timeout2_promise');
resolve();
}).then(function() {
console.log('timeout2_then')
})
})
process.nextTick(function() {
console.log('glob2_nextTick');
})
new Promise(function(resolve) {
console.log('glob2_promise');
resolve();
}).then(function() {
console.log('glob2_then')
})
setImmediate(function() {
console.log('immediate2');
process.nextTick(function() {
console.log('immediate2_nextTick');
})
new Promise(function(resolve) {
console.log('immediate2_promise');
resolve();
}).then(function() {
console.log('immediate2_then')
})
})
複製代碼
1、第一步、首先執行宏任務script。全局入棧。輸出glob1
2、遇到setTimeout,做爲任務源,將指定的任務加入宏任務隊列
3、遇到setImmediate,做爲任務源,將指定的任務加入宏任務隊列
4、遇到process.nextTick,做爲任務源,將指定的任務加入微任務隊列
5、遇到Promise的構造函數,進入執行棧,輸出glob1_promise,Promise.then()做爲任務源,將指定的任務加入微任務
6、遇到setTimeout,做爲任務源,將指定的任務加入宏任務隊列
7、遇到process.nextTick,做爲任務源,將指定的任務加入微任務隊列
8、遇到Promise的構造函數,進入執行棧,輸出glob2_promise,Promise.then()做爲任務源,將指定的任務加入微任務
9、遇到setImmediate,做爲任務源,將指定的任務加入宏任務隊列
10、執行全部微任務隊列,輸出glob1_nextTick和glob2_nextTick、glob1_then、glob2_then
11、循環執行宏任務隊列,執行setTimeout隊列、因爲遇到promise、process.nextTick等任務源,將新的微任務添加到隊列中,而後執行完setTimeout隊列後,再將剛添加進去的微任務所有執行,而後再執行setImmediate,依次循環下去
事件機制其實就是異步任務的通知機制
參考資料: