JavaScript是一門單線程語言,一切JavaScript中的「多線程」都是用單線程模擬出來的,而這主要是靠事件循環機制來實現的javascript
JS引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就回去Event Queue那裏檢查是否有等待被調用的函數。前端
let data = {};
$.ajax({
url: www.javascript.com,
data: data,
success: () => {
console.log("發送成功!");
}
})
console.log("同步代碼執行結束");
複製代碼
success
console.log("同步代碼執行結束");
success
進入Event Queue。success
並執行setTimeout
這個函數是通過指定時間後,把要執行的任務,加入到Event Queue中。又由於主線程任務是一個個執行,若是前面的任務執行須要的時間太長,那麼Event Queue中的任務只能等着,因此可能會致使setTimeout
要執行的任務真正的延遲時間會大於指定時間。java
setTimeout(() => {
task()
},3000)
sleep(10000000)
複製代碼
task
進入Event Table並註冊,計時開始sleep
函數,很慢,很是慢,計時還在繼續timeout
完成,task
進入Event Queue,可是由於sleep
執行得太慢了,還沒執行完,只好等着sleep
終於執行完了,task
終於從Event Queue進入到主線程中執行setTimeout(fn, 0) // 指定某個任務在主線程執行棧爲空後立刻執行,但根據HTML的標準,實際不可能達到0毫秒,最低是4毫秒
複製代碼
setInterval
與setTimeout
相似,不一樣點在於,setInterval
是循環的執行。它會每隔指定的時間,將註冊的函數置入Event Queue,若是前面的任務耗時過久,那麼一樣須要等待。 對於setInterval(fn, ms)
來講,不是每過ms秒會執行一次fn
,而是每過ms秒,會有fn
進入Event Queue。一旦setInterval
的回調函數fn
執行時間超過了延時時間ms,那麼就徹底看不出來有時間間隔了node
除了廣義的同步任務和異步任務,還有對任務有更精確的定義ajax
process.nextTick
相似於node版的setTimeout,在下次循環的下一次循環中調用callback回調函數promise
不一樣類型的任務會進入對應的Event Queue,例如setTimeout
和setInterval
會進入相同的Event Queuebash
事件循環的順序,決定JS代碼的執行順序。進入總體代碼(宏任務)後,開始第一次循環。接着執行這一次循環所產生的全部微任務。而後再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行全部的微任務。多線程
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
複製代碼
setTimeout
,那麼將其回調函數註冊後分發到宏任務Event Queue。(註冊過程與上同,下文再也不描述)Promise
,new Promise
當即執行,並將then
函數分發到微任務Event Queue。then
在微任務Event Queue裏面,執行。題1:異步
async function async1() {
console.log(1);
const result = await async2();
console.log(3);
}
async function async2() {
console.log(2);
}
Promise.resolve().then(() => {
console.log(4);
});
setTimeout(() => {
console.log(5);
});
async1();
console.log(6);
複製代碼
答案: [1,2,6,4,3,5]async
then
函數分發到微任務Event QueuesetTimeout
,將其回調函數註冊後分發到宏任務Event Queueasync1
函數並執行,輸出1async2
輸出2,await意味着後面的代碼要等等了console.log(3)
是在async2
函數返回的Promise的then
函數中執行的,因此講它分發到微任務Event Queue題2:
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
複製代碼
答案: [1,7,6,8, 2,4,3,5, 9,11,10,12]
第一輪事件循環流程分析以下:
console.log
,輸出1。setTimeout
,其回調函數被分發到宏任務Event Queue中。咱們暫且記爲setTimeout1
。process.nextTick()
,其回調函數被分發到微任務Event Queue中。咱們記爲process1
。Promise
,new Promise
直接執行,輸出7。then
被分發到微任務Event Queue中。咱們記爲then1
。setTimeout
,其回調函數被分發到宏任務Event Queue中,咱們記爲setTimeout2
宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
process1
和then1
兩個微任務。process1
,輸出6。then1
,輸出8。好了,第一輪事件循環正式結束,這一輪的結果是輸出1,7,6,8。那麼第二輪時間循環從setTimeout1
宏任務開始:
process.nextTick()
,一樣將其分發到微任務Event Queue中,記爲process2
。new Promise
當即執行輸出4,then
也分發到微任務Event Queue中,記爲then2
。宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout2 | process2 |
then2 |
process2
和then2
兩個微任務能夠執行。setTimeout2
了,執行。process.nextTick()
分發到微任務Event Queue中。記爲process3
。new Promise
,輸出11。then3
。宏任務Event Queue | 微任務Event Queue |
---|---|
process3 | |
then3 |
process3
和then3
。整段代碼,共進行了三次事件循環,完整的輸出爲1,7,6,8,2,4,3,5,9,11,10,12。 (請注意,node環境下的事件監聽依賴libuv與前端環境不徹底相同,輸出順序可能會有偏差)