JS進(線)程,微(宏)任務,同(異)步總結

前言

「JavaScript 是單線程、異步、非阻塞、解釋型腳本語言。」

瀏覽器進程

進程:瀏覽器一個頁面就是新的一個進程,進程是CPU資源分配的最小單位(系統會給它分配內存);javascript

  • Browser進程 (http通訊)
  • 第三方插件進程
  • GPU進程(加速,3D渲染,一次)
  • Renderer進程(新開頁面渲染進程)

圖片說明

Render進程(瀏覽器渲染進程)

線程:線程包含在每一個進程內,線程是CPU調度的最小單位(線程是創建在進程的基礎上的一次程序運行單位,一個進程中能夠有多個線程);java

  • GUI 渲染線程
  • JavaScript引擎線程
  • 定時觸發器線程(宏任務(異步任務))
  • 事件觸發線程(宏任務(異步任務))
  • 異步http請求線程(宏任務(異步任務))
    瀏覽器渲染進程:http://www.javashuo.com/article/p-ykilsgsq-gs.html

1. GUI 渲染線程

  • 1. 解析HTML生成DOM樹 - 渲染引擎首先解析HTML文檔,生成DOM樹
  • 2. 構建Render樹 - 接下來無論是內聯式,外聯式仍是嵌入式引入的CSS樣式會被解析生成CSSOM樹,根據DOM樹與CSSOM樹生成另一棵用於渲染的樹-渲染樹(Render tree),
  • 3. 佈局Render樹 - 而後對渲染樹的每一個節點進行佈局處理,肯定其在屏幕上的顯示位置
  • 4. 繪製Render樹 - 最後遍歷渲染樹並用UI後端層將每個節點繪製出來

2. JavaScript引擎線程(主線程執行棧)

永遠只有JS引擎(JS內核)線程在執行JS腳本程序,
負責解析執行Javascript腳本程序的主線程(例如V8引擎)
  • js引擎執行順序
  • 宏任務(同步任務)直接執行,其餘線程先進入任務隊列等待執行
  • 而後任務隊列中先執行微任務(只有異步任務)
  • 再執行宏任務(異步任務)(若是有任務內還包含宏任務(同步任務),繼續依此執行1)

3. GUI渲染線程與JS引擎線程互斥

因爲JavaScript是可操縱DOM的,若是在修改這些元素屬性同時渲染界面(即JS線程和UI線程同時運行),那麼渲染線程先後得到的元素數據就可能不一致了。
所以爲了防止渲染出現不可預期的結果,瀏覽器設置GUI渲染線程與JS引擎爲互斥的關係,當JS引擎執行時GUI線程會被掛起,GUI更新則會被保存在一個隊列中等到JS引擎線程空閒時當即被執行。node

單線程與多線程

單線程語言:JavaScript 的設計就是爲了處理瀏覽器網頁的交互(DOM操做的處理、UI動畫等),決定了它是一門單線程語言。【處理任務是一件接着一件處理,從上往下順序執行】web

若是有多個線程,它們同時在操做 DOM,那網頁將會一團糟。

當遇到計時器、DOM事件監聽或者是網絡請求的任務時,JS引擎會將它們直接交給** webapi,也就是瀏覽器提供的相應線程(如定時器線程爲setTimeout計時、異步http請求線程處理網絡請求)去處理,而JS引擎線程繼續後面的其餘任務,這樣便實現了異步非阻塞**。後端

同步與異步

JavaScript 中有同步代碼與異步代碼。api

  • 同步:會依次執行,執行完了後便會返回結果
  • 異步:網絡請求、計時器、DOM時間監聽...

事件循環(event loop)和消息隊列(task queue)

  • 事件循環 機制和 消息隊列 的維護是由事件觸發線程控制的。
  • JS引擎線程遇到異步(DOM事件監聽、網絡請求、setTimeout計時器等...),會交給相應的線程單獨去維護異步任務,等待某個時機(計時器結束、網絡請求成功、用戶點擊DOM),而後由事件觸發線程將異步對應的回調函數加入到消息隊列中,消息隊列中的回調函數等待被執行。
  • 同時,JS引擎線程會維護一個執行棧,同步代碼會依次加入執行棧而後執行,結束會退出執行棧。
  • 若是執行棧裏的任務執行完成,即執行棧爲空的時候(即JS引擎線程空閒),事件觸發線程纔會從消息隊列取出一個任務(即異步的回調函數)放入執行棧中執行。
  • 執行完了後,執行棧再次爲空,事件觸發線程會重複上一步操做,再取出一個消息隊列中的任務,這種機制就被稱爲事件循環(event loop)機制。

宏任務與微任務

宏任務(macrotask)

  • 定時觸發器線程(宏任務(異步任務))
  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • 事件觸發線程(宏任務(異步任務))
  • 異步http請求線程(宏任務(異步任務))
  • script方法(宏任務(同步任務))
  • new Promise(宏任務(同步任務)) 當即執行

微任務(microtask)

因爲Es6 和node出現產生的微任務promise

  • Promise.then() catch() finally(),一旦一個pormise有告終果,回調產生一個微任務
  • process.nextTick
  • MutationObserver

執行機制

  • 1. 執行一個宏任務(棧中沒有就從事件隊列中獲取)
  • 2. 執行過程當中若是遇到微任務,就將它添加到微任務的任務隊列中
  • 3. 宏任務執行完畢後,當即執行當前微任務隊列中的全部微任務(依次執行)
  • 4. 當前宏任務執行完畢,開始檢查渲染,而後GUI線程接管渲染
  • 5. 渲染完畢後,JS引擎線程繼續,開始下一個宏任務(從宏任務隊列中獲取)
console.log('script start')
setTimeout(function() {
    console.log('timer over')
}, 0)
Promise.resolve().then(function() {
    console.log('promise1')
}).then(function() {
    console.log('promise2')
})
console.log('script end')
// script start
// script end
// promise1
// promise2
// timer over

JS引擎線程首先執行主代碼塊。瀏覽器

每次執行棧執行的代碼就是一個宏任務,包括任務隊列(宏任務隊列)中的,由於執行棧中的宏任務執行完會去取任務隊列(宏任務隊列)中的任務加入執行棧中,即一樣是事件循環的機制。網絡

在執行宏任務時遇到Promise等,會建立微任務(.then()裏面的回調),並加入到微任務隊列隊尾。多線程

microtask必然是在某個宏任務執行的時候建立的,而在下一個宏任務開始以前,瀏覽器會對頁面從新渲染(task >> 渲染 >> 下一個task(從任務隊列中取一個))。同時,在上一個宏任務執行完成後,渲染頁面以前,會執行當前微任務隊列中的全部微任務。

也就是說,在某一個macrotask執行完後,在從新渲染與開始下一個宏任務以前,就會將在它執行期間產生的全部microtask都執行完畢(在渲染前)。

這樣就能夠解釋 "promise 1" "promise 2" 在 "timer over" 以前打印了。"promise 1" "promise 2" 作爲微任務加入到微任務隊列中,而 "timer over" 作爲宏任務加入到宏任務隊列中,它們同時在等待被執行,可是微任務隊列中的全部微任務都會在開始下一個宏任務以前都被執行完。

在node環境下,process.nextTick的優先級高於Promise,也就是說:在宏任務結束後會先執行微任務隊列中的nextTickQueue,而後纔會執行微任務中的Promise。

吃透這些例子 包你掌握js執行順序及promise知識:
https://www.jianshu.com/p/e585e737fb6f

相關文章
相關標籤/搜索