暑期前端訓練day7——有關vue-diff算法的思考

前言

今天分享一下我對vue的diff的探究,跟我一起深入底層,看一看vue是怎么進行diff的,它的復雜度如何,為什么性能這么高,diff的目標是盡可能的復用原來的真實dom,減少刪除真實dom和創建真實的dom的開銷,我們圍繞這個目標展開。


diff發生的時刻以及目的

首先我們要明確的是diff發生在什么時候,它是發生在updateComponent的時刻,也就是比對新舊虛擬dom,最小化變化真實dom的過程。


過程深究

現在我們來模擬整個過程,執行完render以后的虛擬dom是一顆樹結構,整個過程其實是一個dfs的過程,首先是從跟根節點開始,比對根節點,接著會到下一層節點(注意是一層節點),如果只有一個節點(可能是組件節點或者普通的元素節點,我們都叫節點),那么就會直接比較它們的key和類型,比如他可能是一個組件類型也可能是一個div,如果有一個不一樣就會直接把整個子樹卸載,然后遞歸生成正確的真實dom,當如果不是一個節點,那么其實就是兩組虛擬dom的比對,這個過程是最關鍵的:
因為是兩個列表,vue會為舊虛擬dom列表創建兩個指針,指向頭尾,然后同理,新的也是頭尾兩個指針,然后他會按照:舊列表的頭指針和新列表的頭比較、舊尾和新尾,如果有發現key一樣且類型一樣的,直接復用,如果沒有匹配上,就會按照舊頭和新尾和舊尾新頭就進行對比,有的就直接復用,沒有的話直接進入一個關鍵環節diff的精髓所在:
首先對剩下的沒有處理的新虛擬dom點進行一個映射,map[虛擬dom節點] = id,這個id其實是它在新虛擬dom節點列表中的下標,然后會遍歷舊的dom樹,并且用map[虛擬dom節點] = id記錄下來。接下來,對舊dom列表進行一個匹配,匹配新dom列表中key和類型匹配,因為索引(map的映射值)是遞增的,所以我們會對舊dom列表求一個LIS(最長上升子序列),源碼中的話是通過二分+貪心找到的:

<template>
<div class="app"><h2>最長上升子序列</h2>
</div>
</template><script setup lang="ts">
const arr: Array<number> = [10, 30, 2, 1, 5, 5, 7] 
const inf: number = Math.floor(1e9) 
const lastIdx: Array<number> = new Array(arr.length).fill(-1)   
const q: Array<Array<number>> = [[-inf, 0, -1, -1]]
let c: number = 0 
let idx: number = 1 
let maxLen: number = 0 
for(let n of arr) {if(n > q[idx - 1][0]) {maxLen = Math.max(maxLen, q[idx - 1][1] + 1) q[idx] = [n, q[idx - 1][1] + 1, q[idx - 1][3], c]  lastIdx[c] = q[idx-1][3] idx += 1 c += 1continue }else if(n == q[idx - 1][0]) {c += 1 continue; }let l: number = 0, r: number = q.length - 1while(l < r) {let mid: number = Math.floor((l + r + 1) / 2) if(q[mid][0] <= n) l = mid else r = mid - 1  }let [v, le, last, Idx] = q[l] let va = n, curLen = 1, curLast = -1, curIdx = c if(v < n) {maxLen = Math.max(maxLen, le + 1) curLen = le + 1 curLast = Idx }else if(v === n) {curLen = le curLast = last }l = 0, r = q.length - 1 while(l < r) {let mid = Math.floor((l + r) / 2) if(q[mid][0] > n) r = mid else l = mid + 1  }lastIdx[curIdx] = curLast q[l] = [va, curLen, curLast, curIdx]c += 1 
}console.log(arr); 
console.log(maxLen, q);//比如說找最后一個:
let cur = arr.length - 1 
let res = Array<number>() 
while(cur != -1) {res.push(arr[cur]) cur = lastIdx[cur] 
}
res.reverse() 
console.log(res); </script><style scoped></style>

簡單的二分+貪心只能夠把最長的長度找出來,但是具體的序列找不出來,根據上面的改進能找到具體的序列,源碼大概也是差不多實現方式。
因為這一部分其實就可以保持不動了,然后我們將剩下的新dom點拿出。接著,繼續diff,從舊dom列表從后往前并且從剩下的新dom列表從后往前看,遇到直接匹配的,直接跳過,如果沒有匹配,如果舊dom的map中有,則會通過瀏覽器的方法insertBeforeinsertBeforeinsertBefore直接將這個真實dom點移動到該點位置前面,如果沒有就直接創建然后調用insertBefore方法,關鍵點來了,當前先保留這個點。然后一直進行,最后才將多余的點清空

總的時間復雜度就是O(n + nlogn) * O(insertBefore)

這里講幾個為什么,也是我在寫博客的時候,引發的一些思考:

(1) 為什么不直接全部刪除,然后一次性創建,時間復雜度不是O(n)嗎,如果涉及真實dom移動不是看起來會更慢嗎?
刪除和創建dom的效率是極其慢的,但是移動dom是用瀏覽器底層的方法insertBefore,它是非常高效率的方法,所以我們盡可能避免讓dom創建和刪除,移動是可以接受的。

(2)為什么最后才將多余的點清空。
因為當我們從后往前遍歷的時候,如果我們在當前點操作完了,并且舊點沒用到,如果馬上刪除,并且前面的點可以復用他,但是沒復用到,就可能要刪除這個點,還要創建一個點,代價多了非常大,所以我們最后清除,可以盡可能的復用。

(3) 為什么不直接刪除舊dom列表中不存在于新dom列表中的點?
因為要保留insertBefore的參考位置,保證dom整體結構的穩定性,才會選擇最后刪除,它們的作用其實是作為參照物的作用,也是vue做出的取舍。

筆者水平有限,不夠嚴謹之處勿噴,多多賜教。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92540.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92540.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92540.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Docker】Docker的初步認識以及Ubuntu下的Docker環境安裝、配置

前言 在當今快速迭代的軟件開發與部署領域&#xff0c;容器化技術已成為不可或缺的核心力量&#xff0c;而 Docker 作為容器化技術的杰出代表&#xff0c;正以其輕量、高效、可移植的特性深刻改變著開發與運維的模式。它有效解決了 “在我機器上能運行&#xff0c;在你那里卻不…

【密碼學】2. 古典密碼

目錄2. 古典密碼2.1 經典加密技術基礎2.2 代換技術2.2.1 算術密碼2.2.2 代換密碼&#xff08;Substitution Cipher&#xff09;2.3 置換技術2.4 乘積密碼2.5 歷史上的教訓2. 古典密碼 2.1 經典加密技術基礎 分類 代換&#xff08;Substitution&#xff09;&#xff1a;明文內…

CSS3文本陰影特效全攻略

CSS3文本陰影效果實現 下面我將創建一個展示各種CSS3文本陰影效果的頁面&#xff0c;包含多種樣式示例和代碼實現。 設計思路 創建具有視覺吸引力的標題區域提供多種文本陰影效果實例顯示對應的CSS代碼以供參考添加交互元素讓用戶自定義效果 實現代碼 <!DOCTYPE html&g…

JavaScript 03 嚴格檢查模式Strict字符串類型詳解

2.4 嚴格檢查模式Strict在 JavaScript 里&#xff0c;也是 有 “作用域” 這個說法的。 所以說&#xff0c;變量 也分 全局變量 和 局部變量。 當我們 直接 把 代碼 寫在 script 雙標簽里面的時候&#xff0c;我們 JS 會認為 這只是 一個 沒有名字的 函數&#xff01;&#xff…

車載診斷ECU架構

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 做到欲望極簡,了解自己的真實欲望,不受外在潮流的影響,不盲從,不跟風。把自己的精力全部用在自己。一是去掉多余,凡事找規律,基礎是誠信;二是…

使用vue-pdf-embed發現某些文件不顯示內容

在使用vue-pdf-embed過程中, 突然發現有些pdf文件可以正常打開, 有些文件只顯示了一些數字, 并且控制臺報出如下警告: Warning: loadFont - translateFont failed: “UnknownErrorException: Ensure that the cMapUrl and cMapPacked API parameters are provided.”. Warning…

【設計模式C#】狀態模式(用于解決解耦多種狀態之間的交互)

一種行為設計模式。特點是用類的方式去管理狀態。優點&#xff1a;對每個狀態進行了封裝&#xff0c;提高了代碼的可維護性&#xff1b;減少了條件判斷語句的使用&#xff0c;降低維護成本&#xff1b;易于擴展&#xff0c;每次新增狀態都無需大規模修改其他類&#xff0c;符合…

WebSocket數據通過splice保持現有DOM結構僅更新文本內容?【防閃爍】。

文章目錄 前言 一、DOM更新優化機制 ?1.虛擬DOM復用性 2.?響應式系統觸發 二、性能對比 三、WebSocket場景實踐 ?1.防閃爍原理 2.代碼實現示例 四、特殊注意事項 總結 前言 開發過程中渲染websocket返回的數據時&#xff0c;經常會遇到更新數據閃爍的問題&#xff0c;咱們可…

深入解析Hadoop的Block多副本同步機制與Pipeline復制

Hadoop分布式文件系統概述作為Hadoop生態的核心存儲組件&#xff0c;HDFS&#xff08;Hadoop Distributed File System&#xff09;的設計哲學源于Google File System論文&#xff0c;其架構專門針對大規模數據集處理場景進行了優化。在理解Block多副本同步機制之前&#xff0c…

洪水預報中的序列到序列模型及其可解釋性擴展

洪水預報中的序列到序列模型及其可解釋性擴展 前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家&#xff0c;覺得好請收藏。點擊跳轉到網站。 1. 引言 洪水預報是水文科學和災害管理中的重要課題&#xff…

UniApp 優化實踐:使用常量統一管理本地存儲 Key,提升可維護性

在 UniApp 項目開發中&#xff0c;隨著功能的增加&#xff0c;本地存儲&#xff08;如 uni.setStorageSync&#xff09;的使用頻率也會增加。如果直接在代碼中硬編碼 key 值&#xff0c;不僅容易出錯&#xff0c;也難以后期維護。本文將以“自定義導航欄適配狀態欄高度”為例&a…

計算機網絡:(八)網絡層(中)IP層轉發分組的過程與網際控制報文協議 ICMP

計算機網絡&#xff1a;&#xff08;八&#xff09;網絡層&#xff08;中&#xff09;IP層轉發分組的過程與網際控制報文協議 ICMP前言一、IP層轉發分組的過程第一步&#xff1a;接收數據包并解封裝第二步&#xff1a;提取目標 IP 地址第三步&#xff1a;查詢路由表第四步&…

Python爬蟲實戰:研究concurrent-futures庫相關技術

1. 引言 1.1 研究背景與意義 網絡爬蟲作為互聯網數據采集的重要工具,在信息檢索、輿情分析、學術研究等領域具有廣泛應用。隨著互聯網數據量的爆炸式增長,傳統單線程爬蟲的效率已難以滿足需求,并發爬蟲技術成為研究熱點。 1.2 相關工作 現有爬蟲框架如 Scrapy、Beautifu…

Neo4j 框架 初步簡單使用(基礎增刪改查)

Neo4j 是一個高性能的、開源的圖數據庫。它將數據存儲為圖結構&#xff0c;其中節點表示實體&#xff0c;邊表示實體之間的關系。這種圖數據模型非常適合處理復雜的關系型數據&#xff0c;能夠高效地進行關系查詢和遍歷。 Neo4j 的主要特性包括&#xff1a; 強大的圖查詢語言 C…

【iOS】鎖[特殊字符]

文章目錄前言1??什么是鎖&#x1f512;&#xff1f;1.1 基本概念1.2 鎖的分類2??OC 中的常用鎖2.1 OSSpinLock&#xff08;已棄用&#xff09;&#xff1a;“自旋鎖”的經典代表為什么盡量在開發中不使用自旋鎖自旋鎖的本質缺陷&#xff1a;忙等待&#xff08;Busy Waiting…

在easyui中如何設置自帶的彈窗,有輸入框

這個就是帶input的確認彈框&#xff08;$.messager.prompt&#xff09;// 使用prompt并添加placeholder提示 $.messager.prompt(確認, 確定要將事故記錄標記為 statusText 嗎&#xff1f;, function(r) {if (r) {// r 包含用戶輸入的內容var remark r.trim();// 驗證輸入不為…

Android-API調用學習總結

一、Postman檢查API接口是否支持1.“HTTP Request” 來創建一個新的請求。——請求構建界面&#xff0c;這是你進行所有 API 調用的地方。2.設置請求方法和 URL&#xff1a;選擇請求方法&#xff1a; 在 URL 輸入框左側&#xff0c;有一個下拉菜單。點擊它&#xff0c;選擇你想…

《計算機網絡》實驗報告一 常用網絡命令

目 錄 1、實驗目的 2、實驗環境 3、實驗內容 3.1 ping基本用法 3.2 ifconfig/ipconfig基本用法 3.3 traceroute/tracert基本用法 3.4 arp基本用法 3.5 netstat基本用法 4、實驗結果與分析 4.1 ping命令的基本用法 4.2 ifconfig/ipconfig命令的基本用法 4.3 tracer…

MySQL深度理解-深入理解MySQL索引底層數據結構與算法

1.引言在項目中會遇到各種各樣的慢查詢的問題&#xff0c;對于千萬級的表&#xff0c;如果使用比較笨的查詢方式&#xff0c;查詢一條SQL可能需要幾秒甚至幾十秒&#xff0c;如果將索引設置的比較合理&#xff0c;可以將查詢變得仍然非常快。2.索引的本質索引&#xff1a;幫助M…

Django母嬰商城項目實踐(九)- 商品列表頁模塊

9、商品列表頁模塊 1、業務邏輯 商品模塊分為:商品列表頁 和 商品詳情頁 商品列表頁將所有商品按照一定的規則排序展示,用于可以從銷量、價格、上架時間和收藏數量設置商品的排序方式,并且在商品左側設置分類列表,選擇某一個分類可以篩選出對應的商品信息。 商品列表頁…