淺談js的事件迴圈(Event Loop)

田小菜發表於2019-05-07
  • 事件迴圈是js這門語言的一大特點。
  • 瞭解事件迴圈機制,有助於日常開發中遇到的一些非同步問題。
  • 而且還是前端面試一經常考點。
  • 故本人結合一些文章和個人的一些開發經驗,淺淡一下

一,js是一門單執行緒語言

  1. js的單執行緒
a. js是一門單執行緒的語言。這意味著它在同一時間,只能做同一件事。
b. 但為了協調事件,使用者互動,UI渲染和網路行為互動等。
c. 防止主執行緒被阻塞,Event Loop便應運而生。
如: 傳送一個網路請求,需要等待一定時間,這個時間內主執行緒空閒出來做些其他事;
複製程式碼
  1. 為什麼js是單執行緒?
a. js主要是執行在瀏覽器的腳步語言,主要是操作dom;
b. 舉個例子,如果js同時有多個執行緒。多個執行緒同時操作同一個dom,
   這時瀏覽器該依據那個執行緒,如何判斷優先順序
c. 為了避免上述問題,並降低複雜度,故js被設計成單執行緒語言。
複製程式碼

二,概念的理解

  1. 同步任務
同步任務指的是,在主執行緒上排隊執行的任務,
只有前一個任務執行完畢,才能執行後一個任務;
複製程式碼
  1. 非同步任務
非同步任務指的是,不進入主執行緒、而進入"任務佇列"(task queue)的任務,
只有"任務佇列"通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。
複製程式碼
  1. 非同步執行機制
a. 所有同步任務都在主執行緒上執行,形成一個執行棧(execution context stack);
b. 主執行緒之外,還存在一個"任務佇列"(task queue)。
   只要非同步任務有了執行結果,就在"任務佇列"之中放置一個事件。
c. 一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務佇列",
   看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,
   進入執行棧,開始執行。
d. 主執行緒不斷重複上面的第三步。
複製程式碼
  1. 任務佇列
"任務佇列"是一個先進先出的資料結構,排在前面的事件,優先被主執行緒讀取。
複製程式碼
  1. 事件迴圈
主執行緒從"任務佇列"中讀取事件,這個過程是迴圈不斷的,
所以整個的這種執行機制又稱為Event Loop(事件迴圈)。
複製程式碼
  1. 巨集任務與微任務
非同步任務分為 巨集任務(macrotask) 與 微任務 (microtask),
不同的API註冊的任務會依次進入自身對應的佇列中,
然後等待 Event Loop 將它們依次壓入執行棧中執行。

巨集任務:script(整體程式碼)、setTimeout、setInterval、UI 渲染、 
        I/O、postMessage、 MessageChannel、setImmediate(Node.js 環境)
        
微任務:Promise、 MutaionObserver、process.nextTick(Node.js環境)
複製程式碼
  1. Event Loop(事件迴圈)
1)執行棧選擇最先進入佇列的巨集任務(通常是script整體程式碼),如果有則執行;
(2)檢查是否存在 Microtask,如果存在則不停的執行,直至清空 microtask 佇列;
(3)更新render(每一次事件迴圈,瀏覽器都可能會去更新渲染);
(4)重複以上步驟;
複製程式碼
  1. 巨集任務 > 所有微任務(核心),上程式碼
<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
複製程式碼
  • 上述程式碼的執行結果

淺談js的事件迴圈(Event Loop)

淺談js的事件迴圈(Event Loop)

  • 程式碼結果分析:
1. 巨集任務1=>巨集任務1中的微任務1
   表明執行完巨集任務就執行微任務(忽略巨集任務2,便於理解)
2. 然後到 巨集任務1中巨集任務3=>巨集任務3中的微任務2
   再次表明執行完本巨集任務後就執行本巨集任務下的微任務
3. 最後到微任務1中的巨集任務4
複製程式碼

相關文章