深入聊一聊虛擬DOM與diff算法
虛擬DOM與diff算法
在vue、react等技術(shù)出現(xiàn)之前,每次修改DOM都需要通過遍歷查詢DOM樹的方式,找到需要更新的DOM,然后修改樣式或結(jié)構(gòu),資源損耗十分嚴(yán)重。而對于虛擬DOM來說,每次DOM的更改就變成了JS對象的屬性的更改,能方便的查找JS對象的屬性變化,要比查詢DOM樹的性能開銷小,所以能夠改善瀏覽器的性能問題。
對于vue,從vue2就開始支持虛擬DOM。
diff算法:簡單來說就是找出兩個對象的差異,只對有差異的一小塊DOM進(jìn)行更新,而不是整個DOM,從而達(dá)到最小量更新的效果

虛擬DOM:內(nèi)部會把代碼段解析成一個對象(真實DOM是通過模板編譯變成虛擬DOM的)
用JS對象描述DOM的層次結(jié)構(gòu),DOM中的一切屬性都在虛擬DOM中有對應(yīng)的屬性

snabbdom環(huán)境搭建
是虛擬DOM庫,diff算法的鼻祖,vue源碼借鑒了snabbdom
官方git:https://github.com/snabbdom/snabbdom
git上的snabbdom源碼是用TypeScript寫的,如果要直接使用編譯出來的Javascript版的snabbdom庫,可以從npm上下載npm i -D snabbdom
snabbdom庫是DOM庫,不能在node js環(huán)境運行,需要搭建webpack和webpack-dev-server開發(fā)環(huán)境。需要注意的是必須安裝webpack@5。
npm i -D webpack@5 webpack-cli@3 webpack-dev-server@3
配置webpack.config.js文件,參考官網(wǎng)進(jìn)行配置:https://webpack.docschina.org/
const path = require('path');
module.exports = {
// 入口
entry: './src/index.js',
// 出口
output: {
// 虛擬打包路徑,文件夾不會真正生成,而是在8080端口虛擬生成
publicPath: 'xuni',
// 打包出來的文件名
filename: "bundle.js",
},
// 配置webpack-dev-server
devServer: {
// 端口號
port: 8082,
// 靜態(tài)根目錄
contentBase: 'www',
},
}將項目根目錄下的package.json文件中修改script的配置,就可以通過npm run dev啟動項目

配置完之后將官網(wǎng)的Example進(jìn)行測試,由于示例要獲取id=container的節(jié)點,所以我們需要提前準(zhǔn)備一個id為container的div。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="container"></div>
<script src="/xuni/bundle.js"></script>
</body>
</html>需要注意的點

頁面出現(xiàn)如下狀態(tài)即表示配置完成

虛擬DOM和h函數(shù)
diff是發(fā)生在虛擬DOM上的
新虛擬DOM和老虛擬DOM進(jìn)行diff(精細(xì)化比較),算出應(yīng)該如何最小量更新,最后反映到真正的DOM上。
h函數(shù)用來產(chǎn)生虛擬節(jié)點(vnode)
h('a',{ props: { href: 'https://www.baidu.com' } }, '百度');會得到這樣的虛擬節(jié)點
{ "sel": "a", "data": { props: { href: 'https://www.baidu.com' } }, "text": "百度" }它表示的真正的DOM節(jié)點
<a >百度</a>
如果需要讓虛擬節(jié)點上樹,需要借助patch函數(shù)
import {
init,
classModule,
propsModule,
styleModule,
eventListenersModule,
h,
} from "snabbdom";
// 創(chuàng)建patch函數(shù)
var patch = init([classModule, propsModule, styleModule, eventListenersModule]);
//創(chuàng)建虛擬節(jié)點
var vNode1 = h('a',{ props: { href: 'https://www.baidu.com' } }, '百度');
// 讓虛擬節(jié)點上樹
const container = document.getElementById('container');
patch(container,vNode1);h函數(shù)可以嵌套使用,從而得到虛擬DOM樹
h('ul',{},[
h('li',{},'可樂');
h('li',{},'雪碧');
h('li',{},'椰汁');
])diff算法
實現(xiàn)最小量更新。需要key。key是這個節(jié)點的唯一標(biāo)識,告訴diff算法,在更改前后它們是同一個DOM節(jié)點。
只有同一個虛擬節(jié)點,才進(jìn)行精細(xì)化比較。否則就是暴力刪除舊的、插入新的。
同一個虛擬節(jié)點:選擇器相同且key相同
只進(jìn)行同層比較,不會進(jìn)行跨層比較。
比如下面這兩個DOM節(jié)點,雖然是同一片虛擬節(jié)點,但是跨層了,依舊會暴力刪除舊的、插入新的。
const vnode1 = h('div',{},[
h('p',{ key: 'A' }, 'A'),
h('p',{ key: 'B' }, 'B'),
h('p',{ key: 'C' }, 'C'),
h('p',{ key: 'D' }, 'D'),
]);
const vnode2 = h('div',{},h('section',{},[
h('p',{ key: 'A' }, 'A'),
h('p',{ key: 'B' }, 'B'),
h('p',{ key: 'C' }, 'C'),
h('p',{ key: 'D' }, 'D'),
]))分析源碼也可以驗證以上所述

首先會去判斷是不是虛擬節(jié)點,不是的話會先去把它包裝成虛擬節(jié)點
然后判斷是不是同一個節(jié)點,不是的話插入新的、刪除舊的,是的話精細(xì)化比較
執(zhí)行的流程圖

patch函數(shù)
首先判斷oldVnode是否是虛擬節(jié)點,如果是DOM節(jié)點的話先把oldVnode包裝成虛擬節(jié)點

然后判斷新節(jié)點和舊節(jié)點是否是同一個節(jié)點,判斷key的值是否相同,標(biāo)簽名是否相同,是否都定義了data(data包含一些具體的信息,onclick、style等)

如果不是同一節(jié)點,新節(jié)點直接替換老節(jié)點,刪除舊的、插入新的。在源碼中,創(chuàng)建所有的子節(jié)點時,需要遞歸。

如果新舊節(jié)點是同一個節(jié)點時,會執(zhí)行patchVnode進(jìn)行子節(jié)點比較
patchVnode函數(shù)

首先會找到對應(yīng)的真實DOM
const elm = (vnode.elm = oldVnode.elm)!;
如果新老節(jié)點相同,直接返回 if(oldVnode === vnode) return
如果vnode沒有文本節(jié)點(
isUndef(vnode.text))都有children且不相同
使用updateChildren對比children(diff算法的核心)
只有vnode有children
則oldVnode是一個空標(biāo)簽或者是文本節(jié)點,如果是文本節(jié)點就清空文本節(jié)點,然后將vnode的children創(chuàng)建為真實DOM后插入到空標(biāo)簽內(nèi)。
只有oldVnode有children
vnode沒有的東西,在oldVnode內(nèi)需要刪除掉removeVnodes(oldVnode有且vnode沒有的,都清空或移除)
只有oldVnode有文本
清空文本
如果vnode有text屬性且不同
以vnode為標(biāo)準(zhǔn),無論oldVnode是什么類型節(jié)點,直接設(shè)置為vnode內(nèi)的文本
updateChildren函數(shù)
updateChildren方法的核心:
提取出新老節(jié)點的子節(jié)點:新節(jié)點子節(jié)點ch和老節(jié)點子節(jié)點oldCh
ch和oldCh分別設(shè)置StartIdx(頭指針)和EndIdx(尾指針)變量,相互比較。此時就有四個變量:
oldStartIdx、oldEndIdx、newStartIdx、newEndIdx(這里采用雙指針的思想)有四種方式來比較:
oldStartIdx和newStartIdx比較
如果匹配,DOM不用修改,將oldStartIdx和newStartIdx的下標(biāo)往后移一位
oldEndIdx和newEndIdx比較
如果匹配,DOM不用修改,將oldEndIdx和newEndIdx的下標(biāo)往前移一位
oldStartIdx和newEndIdx比較
如果匹配,DOM不用修改,將oldStartIdx對應(yīng)的真實DOM插入到最后一位,oldStartIdx的下標(biāo)后移一位,newEndIdx的下標(biāo)前移一位。
oldEndIdx和newStartIdx比較
如果匹配,DOM不用修改,將oldEndIdx對應(yīng)的真實DOM插入到oldEndIdx對應(yīng)真實Dom的前面,oldEndIdx的下標(biāo)前移一位,newStartIdx的下標(biāo)后移一位。
如果4種方式都沒有匹配成功,如果設(shè)置了key就通過key進(jìn)行比較,在比較過程中startIdx++,endIdx--,一旦StartIdx > EndIdx表明ch或者oldCh至少有一個已經(jīng)遍歷完成,此時就會結(jié)束比較
處理結(jié)束后,如果新節(jié)點有剩余,就添加;如果舊節(jié)點有剩余,就刪除

v-for中key作用與原理
key是虛擬DOM對象的標(biāo)識,當(dāng)數(shù)據(jù)發(fā)生變化時,Vue會根據(jù)【新數(shù)據(jù)】產(chǎn)生【新的虛擬DOM】,隨后Vue進(jìn)行【新虛擬DOM】與【舊虛擬DOM】的差異比較,比較規(guī)則如下:
(1)舊虛擬DOM中找到了與新虛擬DOM相同的key
?①若虛擬DOM中內(nèi)容沒變,直接使用之前的真實DOM
?②若虛擬DOM中內(nèi)容變了,則生成新的真實DOM,隨后替換掉頁面中之前的真實DOM
(2)舊虛擬DOM中未找到與新虛擬DOM相同的key
?創(chuàng)建新的真實DOM,隨后渲染到頁面
所以,如果用index作為key可能會引發(fā)的問題:
(1)若對數(shù)據(jù)進(jìn)行:逆序添加、逆序刪除等破環(huán)順序操作,會產(chǎn)生沒有必要的真實DOM更新==>界面效果沒問題,但效率低
(2)如果結(jié)構(gòu)中還包含輸入類的DOM,會產(chǎn)生錯誤DOM錯誤==>界面有問題
實際開發(fā)中如何選擇key
最好使用每條數(shù)據(jù)的唯一標(biāo)識作為key,比如id、手機號、身份證號、學(xué)號等唯一值如果不存在對數(shù)據(jù)的逆序添加、逆序刪除等破壞順序操作,僅用于渲染列表用于展示,使用index作為key是沒有問題的
總結(jié)
到此這篇關(guān)于虛擬DOM與diff算法的文章就介紹到這了,更多相關(guān)虛擬DOM與diff算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue框架+Element-ui(el-upload組件)使用http-request方法上傳文件并顯示文件上傳進(jìn)度功能
這篇文章主要介紹了Vue框架+Element-ui(el-upload組件)使用http-request方法上傳文件并顯示文件上傳進(jìn)度功能,本通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08
vue中利用prop進(jìn)行父子通信時的注意事項總結(jié)
這篇文章主要給大家介紹了關(guān)于vue中利用prop進(jìn)行父子通信時的注意事項,文中通過實例介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2022-01-01
webpack+vue中使用別名路徑引用靜態(tài)圖片地址
這篇文章主要介紹了webpack+vue中使用別名路徑引用靜態(tài)圖片地址,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11
詳解vuex之store拆分即多模塊狀態(tài)管理(modules)篇
這篇文章主要介紹了詳解vuex之store拆分即多模塊狀態(tài)管理(modules)篇,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11
在Vue中使用scoped屬性實現(xiàn)樣式隔離的原因解析
scoped是Vue的一個特殊屬性,可以應(yīng)用于<style>標(biāo)簽中的樣式,這篇文章給大家介紹在Vue中,使用scoped屬性為什么可以實現(xiàn)樣式隔離,感興趣的朋友一起看看吧2023-12-12
解決antd Form 表單校驗方法無響應(yīng)的問題
這篇文章主要介紹了解決antd Form 表單校驗方法無響應(yīng)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10

