事件迴圈是js這門語言的一大特點。
瞭解事件迴圈機制,有助於日常開發中遇到的一些非同步問題。
而且還是前端面試一經常考點。
故本人結合一些文章和個人的一些開發經驗,淺淡一下
一,js是一門單執行緒語言
js的單執行緒
a. js是一門單執行緒的語言。這意味著它在同一時間,只能做同一件事。
b. 但為了協調事件,使用者互動,UI渲染和網路行為互動等。
c. 防止主執行緒被阻塞,Event Loop便應運而生。
如: 傳送一個網路請求,需要等待一定時間,這個時間內主執行緒空閒出來做些其他事;
複製程式碼
為什麼js是單執行緒?
a. js主要是執行在瀏覽器的腳步語言,主要是操作dom;
b. 舉個例子,如果js同時有多個執行緒。多個執行緒同時操作同一個dom,
這時瀏覽器該依據那個執行緒,如何判斷優先順序
c. 為了避免上述問題,並降低複雜度,故js被設計成單執行緒語言。
複製程式碼
二,概念的理解
同步任務
同步任務指的是,在主執行緒上排隊執行的任務,
只有前一個任務執行完畢,才能執行後一個任務;
複製程式碼
非同步任務
非同步任務指的是,不進入主執行緒、而進入"任務佇列" (task queue)的任務,
只有"任務佇列" 通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。
複製程式碼
非同步執行機制
a. 所有同步任務都在主執行緒上執行,形成一個執行棧(execution context stack);
b. 主執行緒之外,還存在一個"任務佇列" (task queue)。
只要非同步任務有了執行結果,就在"任務佇列" 之中放置一個事件。
c. 一旦"執行棧" 中的所有同步任務執行完畢,系統就會讀取"任務佇列" ,
看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,
進入執行棧,開始執行。
d. 主執行緒不斷重複上面的第三步。
複製程式碼
任務佇列
"任務佇列" 是一個先進先出的資料結構,排在前面的事件,優先被主執行緒讀取。
複製程式碼
事件迴圈
主執行緒從"任務佇列" 中讀取事件,這個過程是迴圈不斷的,
所以整個的這種執行機制又稱為Event Loop(事件迴圈)。
複製程式碼
巨集任務與微任務
非同步任務分為 巨集任務(macrotask) 與 微任務 (microtask),
不同的API註冊的任務會依次進入自身對應的佇列中,
然後等待 Event Loop 將它們依次壓入執行棧中執行。
巨集任務:script(整體程式碼)、setTimeout、setInterval、UI 渲染、
I/O、postMessage、 MessageChannel、setImmediate(Node.js 環境)
微任務:Promise 、 MutaionObserver、process.nextTick(Node.js環境)
複製程式碼
Event Loop(事件迴圈)
(1 )執行棧選擇最先進入佇列的巨集任務(通常是script整體程式碼),如果有則執行;
(2 )檢查是否存在 Microtask,如果存在則不停的執行,直至清空 microtask 佇列;
(3 )更新render(每一次事件迴圈,瀏覽器都可能會去更新渲染);
(4 )重複以上步驟;
複製程式碼
巨集任務 > 所有微任務(核心),上程式碼
<script>// 巨集任務1
console.log('巨集任務1'); // 巨集任務1中的同步任務
setTimeout(() => {// 巨集任務1中的另一個巨集任務3
console.log('巨集任務1中的另一個巨集任務3');
new Promise((resolve, reject) => {
resolve('巨集任務3中的微任務2');
}).then(data => {// 巨集任務3中的微任務2
console.log(data)
})
}, 300);
new Promise((resolve, reject) => {
resolve('巨集任務1中的微任務1');
}).then(data => {// 巨集任務1中的微任務1
console.log(data);
setTimeout(() => {// 微任務1中的另一個巨集任務4
console.log('微任務1中的另一個巨集任務4');
}, 300);
});
</script>// 巨集任務1
<script>// 巨集任務2
console.log('巨集任務2')
</script>// 巨集任務2
複製程式碼
1. 巨集任務1 =>巨集任務1 中的微任務1
表明執行完巨集任務就執行微任務(忽略巨集任務2 ,便於理解)
2. 然後到 巨集任務1 中巨集任務3 =>巨集任務3 中的微任務2
再次表明執行完本巨集任務後就執行本巨集任務下的微任務
3. 最後到微任務1 中的巨集任務4
複製程式碼