最近在系統的學習Node.js,在學習過程中遇到了Event-loop這個東東,下面把我自己的一些心得進行總結,分享給大家。
Javascript真的是單執行緒執行嗎?
瞭解Javascript的同學應該知道Javascript是單執行緒執行的,比如在程式碼裡你打個alert,系統給你彈個框,後面的程式碼都給阻塞了,事實上也確實如此。因為如果系統給你new Thread的功能,二條執行緒同時更新一個dom,那不打架了麼~~~。
可是,單執行緒的執行模式是很不友好的,在很多場景下,都需要非同步來提高使用者體驗,於是老美那幫哥們就發明了Promise、Callback,setTimeoutt,setInterval,還有我們最常用的Ajax(XMLHttpRequest類) 等非同步程式設計介面,讓我們能開發出,更好使用者體驗的產品。
那麼問題又來了,如果在一個專案裡實用了這些非同步程式設計介面,他們是如何工作的,而且如何有效的控制他們的執行先後順序,這個我們必須要搞清楚,下面就來簡單分享一下。
在開始之前先談談【程式與執行緒】
為什麼要說程式與執行緒,我用最簡單通俗的話來講,大家開發用的瀏覽器Chrome,是一個多程式的瀏覽器;大家在開發時寫的setTimeout,setInterval, XMLHttpRequest類,是在一個程式裡開闢的新的執行緒。
好混亂。別急。畫圖。
從上圖我們可以看到,一個Chrome切頁,就是一個網頁,也就是一個程式。同時在一個網頁裡,我們會寫很多程式碼,裡面有很多API呼叫,如Promise、Callback,setTimeoutt,setInterval,Ajax(XMLHttpRequest類),它們會開啟自己的一個執行緒去執行,網頁(程式)裡包含多個執行緒。
- 瀏覽器事件觸發執行緒(用來控制事件迴圈,存放setTimeout、瀏覽器事件、ajax的回撥函式)
- 定時觸發器執行緒(setTimeout定時器所線上程)
- 非同步HTTP請求執行緒(ajax請求執行緒)
瀏覽器的事件迴圈(Event-loop)
借用網上的圖片。工作原理總結如下:- 所有同步任務都在棧記憶體圖中的(Stack框)的主執行緒上執行,形成一個執行棧,執行棧也表示作用域棧。
- 主執行緒之外,還存在一個任務佇列(callback quere框)。只要非同步任務(一個個執行緒)有了執行結果,就在任務佇列之中放置一個事件(回撥函式)。
- 一旦執行棧中的所有同步任務執行完畢,系統就會讀取任務佇列,將佇列中的事件(回撥函式)放到執行棧中依次執行
- 主執行緒從任務佇列中讀取事件,這個過程是迴圈不斷的。
Node.js的事件迴圈(Event-loop)
借用網上的圖片。工作原理總結如下:- 我們寫的node.js程式碼會交給v8引擎進行處理。
- 程式碼中可能會呼叫node.js Api(如:setTimeout, setInterval, setImmediate, I/O,process.nextTick,Promise等),node會交給libuv庫處理。
- libuv通過阻塞操作和多執行緒實現了非同步io,也就是說libuv內部是完全非同步的,這就是node.js的最大特點事件驅動的核心。
- 通過事件驅動的方式(Callback函式),將結果放到事件佇列中,最終交給我們的應用。
巨集任務和微任務
任務佇列可分為巨集任務和微任務
macro-task(巨集任務): script(全域性任務), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task(微任務): process.nextTick, 原生Promise(有些實現的promise將then方法放到了巨集任務中),Object.observe(已廢棄), MutationObserver