事件迴圈機制 同步與非同步 我們先思考兩個問題,如下: 為什麼會存在同步和非同步的概念? 我們的JavaScript是單線程的,也就是我們的工作流水線的只有一條。如果我們的任務全放在流水線上,其中一個任務出現問題就會阻塞後面的任務,導致我們的工作流水線卡住。因此為了更加高效合理利用這條流水線,在Java ...
目錄
事件迴圈機制
同步與非同步
我們先思考兩個問題,如下:
為什麼會存在同步和非同步的概念?
我們的JavaScript是單線程的,也就是我們的工作流水線的只有一條。如果我們的任務全放在流水線上,其中一個任務出現問題就會阻塞後面的任務,導致我們的工作流水線卡住。因此為了更加高效合理利用這條流水線,在JavaScript中出現了同步與非同步的概念。
同步與非同步任務如何在一條流水線上工作?
同樣是一條流水線。我們的有不同的產品,有的產品能瞬間完成,有的產品需要耗費大量時間才能完成。那麼我們給這堆產品分為兩類,能瞬間完成的產品先放入流水線前面,而需要耗費大量時間的產品則放在流水線後面。將所有的產品按順序從流水線上執行。即使後面有部分產品製作很慢,但我們流水線前面的大部分產品都能順利完成。也就是說,我們能夠及時交付大部分的產品,讓老闆基本滿意就足夠了。後面的產品我們再慢慢做也不遲。
同步
同步任務:我們的任務不會造成流水線阻塞,瞬間就能完成。那我們直接把這些任務定義為同步任務。
同步事件:new Promise()、async關鍵字、console對象方法
非同步
非同步任務:我們的任務可能會造成流水線阻塞,需要時間才能完成。那我們直接把這些任務定義為非同步任務。
非同步事件:我們先不介紹非同步事件,因為非同步事件又分為微任務與巨集任務。我們下麵再介紹。
微任務與巨集任務(非同步事件)
微任務事件:Promise.then()、await 後面的語句、queueMiscrotask、MutationObserver、process.nextTick( node.js環境 )[ 微任務中最先執行 ]
巨集任務事件:setTimout() 和 setInterval()、<script>腳本、I/O、UI交互事件、setImmediate( node.js環境 )[ 巨集任務中最後執行 ]
註意:非同步事件分為微任務和巨集任務,其中微任務優先於巨集任務執行。
任務執行順序
執行棧:同步代碼會首先歸類到此處待執行。按照代碼的書寫順序(上到下)入棧。
微任務隊列:非同步中的微任務代碼歸類到此處。按照代碼的書寫順序(上到下)入隊。當執行棧為空時,微任務會出隊進入執行棧。
巨集任務隊列:非同步中的巨集任務代碼歸類到此處。按照代碼的書寫順序(上到下)入隊。當微任務隊列為空時,巨集任務會出隊進入執行棧。
我們採用驗證法做幾道題
第一道如下:
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
}, 1000)
}, 1000)
new Promise((resolve, reject) => {
console.log(4)
resolve(5) // 之後會調用 then 方法
console.log(6)
}).then((resolve, reject) => {
console.log(resolve)
})
function add(a, b) {
return a + b
}
async function foo() {
console.log(7)
let num = await add(4, 4)
console.log(9)
return num
}
foo().then((num) => {
console.log(num)
})
console.log(10) // 1 4 6 7 10 5 9 8 2 3
以上代碼沒有出現巨集任務嵌套微任務,微任務嵌套巨集任務的情況。那麼我們可以按編號(對應的輸出語句)直接對執行棧、微任務和巨集任務分類如下:
第一次分析執行棧(同步任務):1 4 6 7 10
第二次分析微任務:5 9 8
第三次分析巨集任務:2 3
然後我們按執行順序將答案組合在一起。最終答案:1 4 6 7 10 5 9 8 2 3。
註意:以上分析方法只能應對非同步函數無嵌套的情況。
接下來我們增加難度,巨集任務嵌套微任務,微任務嵌套巨集任務。第二題如下:
console.log(1)
setTimeout(() => {
console.log(2)
new Promise((resolve, reject) => {
console.log(3)
resolve(4)
}).then((resolve, reject) => {
console.log(resolve)
})
console.log(5)
}, 1000)
console.log(6)
new Promise((resolve, reject) => {
console.log(7)
resolve(8)
setTimeout(() => {
console.log(9)
}, 1000)
}).then((resolve, reject) => {
console.log(resolve)
})
console.log(10) // 1 6 7 10 8 2 3 5 4 9
以上代碼出現了巨集任務嵌套微任務,微任務嵌套巨集任務的情況。
提示:外部(全局作用域)的代碼可以按任務執行順序分析,非同步函數內部其實也可以按照我們的任務執行順序分析。其分析過程是一樣的。
最終答案:1 6 7 10 8 2 3 5 4 9。還有很多特例,可自行編寫非同步函數測試。總結方法
最終總結
分析方法:先對代碼進行事件分類,然後分別放入對應類型的三個地方(執行棧、微任務隊列和巨集任務隊列)再按照我們的執行順序,發生非同步函數嵌套情況時,也可以對其內部應用這套規則解決。
任務執行順序如下:
- 同步(new Promise()、async關鍵字、console對象方法)
- process.nextTick(node.js環境)
- 微任務(非同步)(Promise().then()、await 後面的語句)
- 巨集任務(非同步)(定時器函數、<script>腳本、I/O、UI交互事件)
- setImmediate(node.js環境)(當前事件迴圈結束後執行)
參考