Event Loop - JavaScript和node運行機制

JavaScript 運行機制

單線程(單一主線程)

想必你們都瞭解,JavaScript語言最大的特色之一就是單線程,什麼是單線程呢?vue

就是同一時間只能執行一件事情,這麼設計的主要緣由是爲了防止用戶在操做時出現衝突,例如兩個線程同時處理一個DOM那麼以誰爲準呢?node

在H5中新增了一個後臺運行的線程Web Workers,可是這個線程是受主線程控制的而且不能操做DOM。web

Event Loop(事件環)

圖片轉引自Philip Roberts的演講《Help, I'm stuck in an event-loop》 ajax

JS在運行時造成一個heap(堆)和一個stack(執行棧),

存放在堆(heap)內存中的都是對象,棧裏面的變量實際保存的是一個指針,這個指針指向堆(heap)內存中的對象。promise

執行棧裏面的代碼開始執行,棧裏面的方法可能調用webAPI(操做DOM, ajax, 定時器)
將他們的回調加入callback queue(任務隊列)
例如 在stack裏面執行了ajax,當ajax運行完成後在callback queue裏面加ajax的回調,
定時器同樣,必須得定時器到時間纔會將其回調函數加入callback queue
任務隊列裏面的回調都是異步的回調瀏覽器

console.log(1);
let fn = () => { console.log(2) }
setTimeout(fn)
console.log(3)
// 執行 console.log(1) console.log(3) 等待定時器到期將fn加入事件隊列
// 只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。
// 
複製代碼

須要注意的是,setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。要是當前代碼耗時很長,有可能要等好久,因此並無辦法保證,回調函數必定會在setTimeout()指定的時間執行。bash

callback queue遵循先進先出的邏輯,先被加入隊列的回調會被先執行異步

宏任務與微任務

看一段代碼socket

// 思考一下執行結果的順序會是什麼
Promise.resolve().then(() => {
    console.log(1);
})
console.log(2);
setTimeout(() => {
    console.log(3);
})
// 結果是 2 1 3
複製代碼

驚不驚喜?難不成promise.then和setTimeout同樣?那咱們換一個順序函數

console.log(2);
setTimeout(() => {
    console.log(3);
})
Promise.resolve().then(() => {
    console.log(1);
})
// 結果依舊是 2 1 3
複製代碼

這是宏任務與微任務的緣由
Promise.then是微任務,setTimeout是宏任務, 微任務在執行棧中代碼走完後當即執行,在宏任務以前執行,全部微任務執行完再執行宏任務

宏任務

setTimeout setInterval (setImmediate)

微任務

Promise.then,瀏覽器把它的實現放到了微任務中,MutationObserve不兼容, MessageChannel微任務(vue中nextTick實現原理)

再看一段代碼

console.log(1);
setTimeout(function(){
    console.log(2);
    Promise.resolve(1).then(function(){
        console.log('promise')
    })
})
setTimeout(function(){
    console.log(3);
})
複製代碼

帶瀏覽器中輸入結果的順序是 1 2 prmise 3
先走執行棧 console.log(1); 先走第一個setTimeout,將微任務放到隊列中,執行微任務,微任務執行完再走宏任務 (瀏覽器過程)

可是在node裏面執行就不是這麼回事了
node裏面執行結果是1 2 3 promise
node是將當前任務隊列裏面的全部回調走完再走微任務的回調隊列

你沒聽錯,微任務有一個本身的執行隊列

Web Workers

Web Worker爲Web內容在後臺線程中運行腳本提供了一種簡單的方法。線程能夠執行任務而不干擾用戶界面, 意思就是在執行Web Worker裏面腳本時,頁面不會假死。

簡單說一下專用worker用法,一個專用worker僅僅能被生成它的腳本所使用

main.js(主線程文件)

if (window.worker) { // 處理錯誤兼容
    let myWorker = new Worker('worker.js'); // 指定一個腳本的URI
    
    // 獲取兩個input輸入框
    let first = document.getElementById('first')
    let first = document.getElementById('second')
    
    // workers的方法經過postMessage()方法和onmessage事件處理函數生效
    first.onchange = function() {
        myWorker.postMessage([first.value,second.value]);
        console.log('Message posted to worker');
    }

    second.onchange = function() {
        myWorker.postMessage([first.value,second.value]);
        console.log('Message posted to worker');
    }
    
    myWorker.onmessage = function(e) { // 獲取worker.js 的返回結果
      result.textContent = e.data;
      console.log('Message received from worker');
    }
}
複製代碼
worker.js

onmessage = function(e) {
  // 傳進來的參數都在e.data裏面
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult); // 返回數據
}
複製代碼

詳細瞭解請點擊

node

node 的 Event Loop

當Node.js啓動時,它會初始化事件循環,這可能會調用異步API調用,定時器或調用 process.nextTick(),而後開始處理事件循環。

下圖顯示了事件循環的操做順序的簡化概述。

注意:每一個方框將被稱爲事件循環的「階段」。

每一個階段都有一個執行回調的FIFO(先入先出 first in,first out)隊列。

階段概述

  • 定時器: 此階段執行由setTimeout()和setInterval()定義的回調。
  • I/O回調: 此階段執行幾乎全部的回調,除了關閉回調,定時器和setImmediate().
  • idle, prepare: 只在內部使用。
  • 輪詢: 檢索新的I / O事件。檢查是否有定時器到期。
  • 檢查: setImmediate()在這裏調用回調。
  • 關閉回調: socket.on('close', ...)等。

詳細階段

計時器

計時器指定時間以後能夠執行提供的回調,但不會當即執行,只是把回調放入timers階段的隊列中。
注意:技術上講,輪詢階段控制什麼時候執行定時器

I/O回調

此階段爲某些系統操做(讀寫文件等)執行回調。

輪詢

該階段有兩個主要功能
1.執行以及到時間的定時器
2.處理輪詢隊列中的事件

當進入此階段而且沒有定時器時:

  • 若是輪詢隊列不爲空,則事件循環將遍歷其回調隊列,同步執行它們,直到隊列耗盡或達到系統相關硬限制。

  • 若是輪詢隊列爲空,則會發生如下兩件事之一:

    • 若是setImmediate()已經被調用,那就結束輪詢階段進入檢查階段
    • 若是沒有setImmediate()調用,事件循環將等待回調被添加到隊列中,而後當即執行它們

一旦輪詢隊列爲空,事件循環將檢查已達到時間的定時器。若是一個或多個定時器準備就緒,則事件循環將回退到定時器階段以執行這些定時器的回調。

檢查

執行setImmediate()的階段

node裏面的微任務

node 裏面的微任務有 then 和 nextTick

他們不是事件循環裏面的一部分微任務有本身的隊列,不管事件循環在任何階段,微任務隊列都將在當前操做完成後處理。也就是當前階段結束,下一個階段開始以前清空微任務隊列。

最後

有理解不對的地方還請多多指教。

相關文章
相關標籤/搜索