前端基本功(五):瞭解javascript的運行機制(單線程、任務隊列、EventLoop、微任務、宏任務)

1. 單線程特色

  1. 單線程能夠避免多線程操做帶來的複雜的同步問題。
  2. HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。
  3. 咱們必須清楚一點,觸發和執行並非同一律念,計時器的回調函數必定會在指定delay的時間後被觸發,但並不必定當即執行,可能須要等待。全部JavaScript代碼是在一個線程裏執行的,像鼠標點擊和計時器之類的事件只有在JS單線程空閒時才執行。

2. 任務隊列

  1. 全部同步任務都在主線程上執行,造成一個執行棧。
  2. 主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
  3. 一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
  4. 只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。

3. Event Loop

JS 會建立一個相似於 while (true) 的循環,每執行一次循環體的過程稱之爲 Tick。每次 Tick 的過程就是查看是否有待處理事件,若是有則取出相關事件及回調函數放入執行棧中由主線程執行。待處理的事件會存儲在一個任務隊列中,也就是每次 Tick 會查看任務隊列中是否有須要執行的任務。javascript

event loop

異步操做會將相關回調添加到任務隊列中。而不一樣的異步操做添加到任務隊列的時機也不一樣,如 onclick, setTimeout, ajax 處理的方式都不一樣,這些異步操做是由瀏覽器內核的 webcore 來執行的,webcore 包含上圖中的3種 webAPI,分別是 DOM Binding、network、timer模塊。java

​ 1. onclick 由瀏覽器內核的 DOM Binding 模塊來處理,當事件觸發的時候,回調函數會當即添加到任務隊列中。node

​ 2. setTimeout 會由瀏覽器內核的 timer 模塊來進行延時處理,當時間到達的時候,纔會將回調函數添加到任務隊列中。web

​ 3. ajax 則會由瀏覽器內核的 network 模塊來處理,在網絡請求完成返回以後,纔將回調添加到任務隊列中。面試

任務隊列是在事件循環之上的,事件循環每次 tick 後會查看 ES6 的任務隊列中是否有任務要執行,也就是 ES6 的任務隊列比事件循環中的任務(事件)隊列優先級更高。如 Promise 就使用了 ES6 的任務隊列特性。ajax

4. javascript是單線程的,瀏覽器是多線程的。

瀏覽器的內核是多線程的,它們在內核控制下相互配合以保持同步,一個瀏覽器至少實現三個常駐線程:JavaScript引擎線程,GUI渲染線程,瀏覽器事件觸發線程。數據庫

  1. JavaScript引擎是基於事件驅動單線程執行的,JavaScript引擎一直等待着任務隊列中任務的到來,而後加以處理,瀏覽器不管何時都只有一個JavaScript線程在運行JavaScript程序。
  2. GUI渲染線程負責渲染瀏覽器界面,當界面須要重繪(Repaint)或因爲某種操做引起迴流(Reflow)時,該線程就會執行。但須要注意,GUI渲染線程與JavaScript引擎是互斥的,當JavaScript引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JavaScript引擎空閒時當即被執行。
  3. 事件觸發線程,當一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待JavaScript引擎的處理。這些事件可來自JavaScript引擎當前執行的代碼塊如setTimeout、也可來自瀏覽器內核的其餘線程如鼠標點擊、Ajax異步請求等,但因爲JavaScript的單線程關係全部這些事件都得排隊等待JavaScript引擎處理(當線程中沒有執行任何同步代碼的前提下才會執行異步代碼)。

5. 多線程的優勢和缺點分別是什麼?

優勢:一、將耗時較長的操做(網絡請求、圖片下載、音頻下載、數據庫訪問等)放在子線程中執行,能夠防止主線程的卡死;二、能夠發揮多核處理的優點,提高cpu的使用率。 缺點:一、每開闢一個子線程就消耗必定的資源; 二、會形成代碼的可讀性變差;三、若是出現多個線程同時訪問一個資源,會出現資源爭奪的狀況。promise

6. 瀏覽器的event loop至少包含兩個隊列,macrotask隊列和microtask隊列

  1. microtask 即微任務,是由js引擎分發的任務,老是添加到當前任務隊列末尾執行。另外在處理microtask期間,若是有新添加的microtasks,也會被添加到隊列的末尾並執行: Promise、MutaionObserver、process.nextTick(Node.js 環境)
  2. macrotask隊列 等同於咱們常說的任務隊列,macrotask是由宿主環境分發的異步任務,事件輪詢的時候老是一個一個任務隊列去查看執行的,"任務隊列"是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取:script(總體代碼)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(Node.js 環境)
  3. 只要有微任務咱們確定是執行微任務的,當前進行的會執行,當前執行完的若是執行完後event loop仍是檢測到微任務,仍是執行微任務,檢測出沒有微任務,咱們就執行宏任務隊列中的任務。
  4. 經典面試題:
async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start')
setTimeout(function(){
    console.log('setTimeout') 
},0)  
async1();
new Promise(function(resolve){
    console.log('promise1')
    resolve();
}).then(function(){
    console.log('promise2')
})
console.log('script end')
/* 解題思路: 首先按照代碼的執行順序從上往下,js始終都是單線程的,先執行的確定是同步任務,再根據進入任務隊列的順序先進先出,先微後宏。 微任務是一次性將隊列中存在的微任務執行完畢,宏任務是一個一個先進先出。 Promise是一個構造函數,調用的時候會生成Promise實例。當Promise的狀態改變時會調用then函數中定義的回調函數。 咱們都知道這個回調函數不會馬上執行,他是一個微任務會被添加到當前任務隊列中的末尾,在下一輪任務開始執行以前執行。 async/await成對出現,async標記的函數會返回一個Promise對象,可使用then方法添加回調函數。await後面的語句會同步執行。但 await 下面的語句會被當成微任務添加到當前任務隊列的末尾異步執行。 */

/* 答案: > node8版本: script start -> async1 start -> async2 -> promise1 -> script end -> promise2 -> async1 end -> setTimeout <= node8版本: script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> setTimeout 這主要是node.js8版本與其餘版本的差別,他們對await的執行方法不一樣 */
複製代碼
相關文章
相關標籤/搜索