自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為啥同樣的邏輯在不同前端框架中效果不同

開發(fā) 前端
前端框架中經(jīng)常有「將多個(gè)自變量變化觸發(fā)的更新合并為一次執(zhí)行」的批處理場(chǎng)景,框架的類型不同,批處理的時(shí)機(jī)也不同。

[[435176]]

大家好,我卡頌。

前端框架中經(jīng)常有「將多個(gè)自變量變化觸發(fā)的更新合并為一次執(zhí)行」的批處理場(chǎng)景,框架的類型不同,批處理的時(shí)機(jī)也不同。

比如如下Svelte代碼,點(diǎn)擊H1后執(zhí)行onClick回調(diào)函數(shù),觸發(fā)三次更新。由于批處理,三次更新會(huì)合并為一次。

接著分別以同步、微任務(wù)、宏任務(wù)的形式打印渲染結(jié)果:

  1. <script> 
  2.   let count = 0; 
  3.   let dom; 
  4.   const onClick = () => { 
  5.     // 三次更新合并為一次 
  6.     count++; 
  7.     count++; 
  8.     count++; 
  9.    
  10.     console.log("同步結(jié)果:", dom.innerText); 
  11.    
  12.     Promise.resolve().then(() => { 
  13.       console.log("微任務(wù)結(jié)果:", dom.innerText); 
  14.     }); 
  15.    
  16.     setTimeout(() => { 
  17.       console.log("宏任務(wù)結(jié)果:", dom.innerText); 
  18.     }); 
  19.   } 
  20. </script> 
  21.  
  22. <h1 bind:this={dom} on:click={onClick}>{count}</h1> 

同樣的邏輯用不同框架實(shí)現(xiàn),打印結(jié)果如下:

  • Vue3:同步結(jié)果:0 微任務(wù)結(jié)果:3 宏任務(wù)結(jié)果:3
  • Svelte:同步結(jié)果:0 微任務(wù)結(jié)果:3 宏任務(wù)結(jié)果:3
  • Legacy React:同步結(jié)果:0 微任務(wù)結(jié)果:3 宏任務(wù)結(jié)果:3
  • Concurrent React:同步結(jié)果:0 微任務(wù)結(jié)果:0 宏任務(wù)結(jié)果:3

4種實(shí)現(xiàn)的Demo地址:React[1]Vue3[2]Svelte[3]

本質(zhì)原因在于:有的框架使用宏任務(wù)實(shí)現(xiàn)批處理,有的框架使用微任務(wù)實(shí)現(xiàn)批處理。

本文接下來會(huì)講解宏任務(wù)、微任務(wù)的起源,以及他們與批處理的關(guān)系。

如何調(diào)度任務(wù)

先放上完整流程圖,方便有個(gè)整體印象:

事件循環(huán)流程圖

默認(rèn)情況下,瀏覽器(以Chrome為例)中每個(gè)Tab頁對(duì)應(yīng)一個(gè)渲染進(jìn)程,渲染進(jìn)程包含主線程、合成線程、IO線程等多個(gè)線程。

主線程的工作非常繁忙,要處理DOM、計(jì)算樣式、處理布局、處理事件響應(yīng)、執(zhí)行JS等。

這里有兩個(gè)問題需要解決:

  1. 這些任務(wù)不僅來自線程內(nèi)部,也可能來自外部,如何調(diào)度這些任務(wù)?
  2. 主線程在工作過程中,新任務(wù)如何參與調(diào)度?

第一個(gè)問題的答案是:「消息隊(duì)列」

所有參與調(diào)度的任務(wù)會(huì)加入任務(wù)隊(duì)列中。根據(jù)隊(duì)列「先進(jìn)先出」的特性,最早入隊(duì)的任務(wù)會(huì)被最先處理。用偽代碼描述如下:

  1. // 從任務(wù)隊(duì)列中取出任務(wù) 
  2. const task = taskQueue.takeTask(); 
  3. // 執(zhí)行任務(wù) 
  4. processTask(task); 

其他進(jìn)程通過IPC將任務(wù)發(fā)送給渲染進(jìn)程的IO線程,IO線程再將任務(wù)發(fā)送給主線程的任務(wù)隊(duì)列,比如:

  • 鼠標(biāo)點(diǎn)擊后,瀏覽器進(jìn)程通過IPC將“點(diǎn)擊事件”發(fā)送給IO線程,IO線程將其發(fā)送給任務(wù)隊(duì)列
  • 資源加載完成后,網(wǎng)絡(luò)進(jìn)程通過IPC將“加載完成事件”發(fā)送給IO線程,IO線程將其發(fā)送給任務(wù)隊(duì)列

如何調(diào)度新任務(wù)

第二個(gè)問題的答案是:「事件循環(huán)」

主線程會(huì)在循環(huán)語句中執(zhí)行任務(wù)。隨著循環(huán)一直進(jìn)行下去,新加入的任務(wù)會(huì)插入隊(duì)列末尾,老任務(wù)會(huì)被取出執(zhí)行。用偽代碼描述如下:

  1. // 退出事件循環(huán)的標(biāo)識(shí) 
  2. let keepRunning = true
  3.  
  4. // 主線程 
  5. function MainThread() { 
  6.   // 循環(huán)執(zhí)行任務(wù) 
  7.   while(true) { 
  8.     // 從任務(wù)隊(duì)列中取出任務(wù) 
  9.     const task = taskQueue.takeTask(); 
  10.     // 執(zhí)行任務(wù) 
  11.     processTask(task); 
  12.  
  13.     if (!keepRunning) { 
  14.       break; 
  15.     } 
  16.   } 

延遲任務(wù)

除了任務(wù)隊(duì)列,瀏覽器還根據(jù)WHATWG標(biāo)準(zhǔn),實(shí)現(xiàn)了延遲隊(duì)列,用于存放需要被延遲執(zhí)行的任務(wù)(如setTimeout),偽代碼如下:

  1. function MainThread() { 
  2.   while(true) { 
  3.     const task = taskQueue.takeTask(); 
  4.     processTask(task); 
  5.  
  6.     //執(zhí)行延遲隊(duì)列中的任務(wù)  
  7.     processDelayTask() 
  8.  
  9.     if (!keepRunning) { 
  10.       break; 
  11.     } 
  12.   } 

當(dāng)本輪循環(huán)任務(wù)執(zhí)行完后(即執(zhí)行完processTask后),會(huì)執(zhí)行processDelayTask檢查是否有延遲任務(wù)到期,如果有任務(wù)過期則執(zhí)行他。

介于processDelayTask的執(zhí)行時(shí)機(jī)在processTask之后,所以當(dāng)任務(wù)的執(zhí)行時(shí)間比較長,可能會(huì)導(dǎo)致延遲任務(wù)無法按期執(zhí)行??紤]如下代碼:

  1. function sayHello() { console.log('hello') } 
  2.  
  3. function test() {  
  4.   setTimeout(sayHello, 0);  
  5.   for (let i = 0; i < 5000; i++) { 
  6.     console.log(i); 
  7.   } 
  8. test() 

即使將延遲任務(wù)sayHello的延遲時(shí)間設(shè)為0,也需要等待test所在任務(wù)執(zhí)行完后才能執(zhí)行,所以sayHello最終的延遲時(shí)間是大于設(shè)定時(shí)間的。

宏任務(wù)與微任務(wù)

加入任務(wù)隊(duì)列的新任務(wù)需要等待隊(duì)列中其他任務(wù)都執(zhí)行完后才能執(zhí)行,這對(duì)于「突發(fā)情況下需要優(yōu)先執(zhí)行的任務(wù)」是不利的。

為了解決時(shí)效性問題,任務(wù)隊(duì)列中的任務(wù)被稱為宏任務(wù),在宏任務(wù)執(zhí)行過程中可以產(chǎn)生微任務(wù),保存在該任務(wù)執(zhí)行上下文中的微任務(wù)隊(duì)列中。

即流程圖中右邊的部分:

事件循環(huán)流程圖

在宏任務(wù)執(zhí)行結(jié)束前會(huì)遍歷其微任務(wù)隊(duì)列,將該宏任務(wù)執(zhí)行過程中產(chǎn)生的微任務(wù)批量執(zhí)行。

MutationObserver

微任務(wù)是如何解決時(shí)效性問題同時(shí)又兼顧性能呢?

考慮用于監(jiān)控DOM變化的微任務(wù)API —— MutationObserver。

當(dāng)同一個(gè)宏任務(wù)中發(fā)生多次DOM變化,會(huì)產(chǎn)生多個(gè)MutationObserver微任務(wù),其執(zhí)行時(shí)機(jī)是該宏任務(wù)執(zhí)行結(jié)束前,相比于作為新的宏任務(wù)進(jìn)入隊(duì)列等待執(zhí)行,保證了時(shí)效性。

同時(shí),由于微任務(wù)隊(duì)列內(nèi)的微任務(wù)被批量執(zhí)行,相比于每次DOM變化都同步執(zhí)行回調(diào),性能更佳。

總結(jié)

框架中批處理的實(shí)現(xiàn)本質(zhì)和MutationObserver非常類似。利用了宏任務(wù)、微任務(wù)異步執(zhí)行的特性,將更新打包后執(zhí)行。

只不過不同框架由于更新粒度不同,比如Vue3、Svelte更新粒度很細(xì),所以使用微任務(wù)實(shí)現(xiàn)批處理。

React更新粒度很粗,但內(nèi)部實(shí)現(xiàn)復(fù)雜,即有宏任務(wù)場(chǎng)景也有微任務(wù)的場(chǎng)景。

參考資料

[1]React:

https://codesandbox.io/s/react-concurrent-mode-demo-forked-t8mil?file=/src/index.js[2]Vue3:

https://codesandbox.io/s/crazy-rosalind-wqj0c?file=/src/App.vue[3]Svelte:

https://svelte.dev/repl/1e4e4e44b9ca4e0ebba98ef314cfda54?version=3.44.1

 

責(zé)任編輯:姜華 來源: 魔術(shù)師卡頌
相關(guān)推薦

2012-04-20 15:52:48

div

2021-12-06 15:35:01

CSS前端開發(fā)

2019-10-14 10:09:28

ApacheHiveSpark

2025-01-08 08:59:48

2011-04-19 10:53:05

SSAS

2010-08-17 15:21:17

IEFirefoxHTML

2024-12-17 16:26:31

2009-12-15 16:41:17

路由器設(shè)置

2010-07-21 15:07:30

telnet服務(wù)

2010-12-23 13:56:55

SharePointIntranet

2009-07-02 13:40:45

面試

2023-09-27 07:13:59

Spring框架通信

2023-01-03 07:49:45

Java隨機(jī)數(shù)線程

2021-12-25 23:17:52

Windows 11Windows微軟

2025-01-07 08:17:37

SQLEM數(shù)據(jù)庫所

2010-08-24 10:53:49

CSSpaddingIE

2013-08-27 12:42:42

瀏覽器

2013-11-08 14:43:37

服務(wù)商輕應(yīng)用

2010-11-30 11:00:10

數(shù)據(jù)中心指標(biāo)

2021-06-18 05:54:27

MongoDB數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)