日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

虚拟DOM和Diff算法 - 入门级

發(fā)布時(shí)間:2025/3/20 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 虚拟DOM和Diff算法 - 入门级 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

什么是虛擬Dom

我們知道我們平時(shí)的頁面都是有很多Dom組成,那虛擬Dom(virtual dom)到底是什么,簡單來講,就是將真實(shí)的dom節(jié)點(diǎn)用JavaScript來模擬出來,而Dom變化的對比,放到 Js 層來做。

下面是一個(gè)傳統(tǒng)的dom節(jié)點(diǎn),大家肯定都不陌生。

而這個(gè)dom對應(yīng)的虛擬dom,可以表示成下面的樣子

很簡單,大家都能看懂,tag表示標(biāo)簽名,attrs就是dom的屬性,每個(gè)dom如果有children的話,就會(huì)在children中以數(shù)組的形式展示,數(shù)組的每一項(xiàng)就又是一個(gè)虛擬dom結(jié)構(gòu)。

這里使用 Js 來實(shí)現(xiàn)虛擬dom的原因是 Js 在前端領(lǐng)域,是唯一一門圖靈完備的語言;所謂圖靈完備語言,就是指可以進(jìn)行復(fù)雜邏輯操作,實(shí)現(xiàn)各種邏輯算法語言。

為何使用虛擬Dom

有人會(huì)問,dom挺好啊,我們剛學(xué)前端的時(shí)候肯定會(huì)接觸JQuery,JQuery就是典型的操作dom的一個(gè)框架工具庫,我們拿JQuery來設(shè)計(jì)一個(gè)場景,來解釋一下虛擬dom的用處及價(jià)值。

這有一個(gè)需求場景

var data = [{name: '張三',age: '20',address: '杭州'},{name: '李四',age: '22',address: '北京'},{name: '隔壁老王',age: '24',address: "西溪水岸"}] 復(fù)制代碼

我們現(xiàn)在想要將這個(gè)數(shù)據(jù)渲染成一個(gè)表格,并點(diǎn)擊頁面上的按鈕更換我們的部分?jǐn)?shù)據(jù),我們使用Jquery來做。

<div id="container"></div><button id="btn-change">change</button><script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script><script>var data = [{name: '張三',age: '20',address: '杭州'},{name: '李四',age: '22',address: '北京'},{name: '隔壁老王',age: '24',address: "西溪水岸"}]function render(data) {var $container = $('#container')//清空現(xiàn)有內(nèi)容$container.html('')// 拼接 tablevar $table = $('<table>')$table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>'))data.forEach(function (item) {$table.append($('<tr><td>'+ item.name +'</td><td>'+item.age+'</td><td>'+item.address+'</td></tr>'))})// 渲染到頁面$container.append($table)}$('#btn-change').click(function () {data[1].age = 30data[2].address = '上海'render(data)})// 初始化時(shí)候渲染render(data) 復(fù)制代碼

可以看到,我們將data的第2項(xiàng)的age和 第3項(xiàng)的address數(shù)據(jù)更換了,點(diǎn)擊change按鈕:

vdom解決的問題

我們可以從圖中看到,我們只是更改了表格的部分?jǐn)?shù)據(jù),但是整個(gè)tabel節(jié)點(diǎn)就全部閃爍,說明整個(gè)table都被替換了一遍。

這個(gè)合乎常理的JQuery操,及時(shí)是web頁面性能的巨大殺手。因?yàn)樗牧瞬恍枰牡膁om節(jié)點(diǎn),如果你還不能事情的嚴(yán)重性,可以繼續(xù)往下看。

下面的代碼的操作很簡單,創(chuàng)建一個(gè)空的div標(biāo)簽,循環(huán)遍歷其中的屬性并將其拼打印出來

var div = document.createElement('div')var item ,result = ''for (item in div) {result += ' | ' + item}console.log(result) 復(fù)制代碼

密密麻麻的屬性,更何況這還只是一級屬性,可想而知直接操作dom的方式是有多么費(fèi)時(shí),dom操作是費(fèi)時(shí)的,但是Js作為一門語言,運(yùn)行速度是非常快的,我們?nèi)绻贘s層做dom對比,盡量減少不必要的dom操作,而不是每一次都全部翻修,我們的效率就會(huì)大大增加。而vdom就可以完美解決這個(gè)問題。

如何使用虛擬dom

說了這么多虛擬dom的好,有同學(xué)會(huì)問,如何使用虛擬dom呢?

要了解如何使用vdom,我們可以借助現(xiàn)有的vdom實(shí)現(xiàn)庫,來了解其API,進(jìn)而了解如何將vdom運(yùn)用于開發(fā)中。

這里我們選擇一個(gè)Vue2中使用的虛擬dom庫 snabbdom,下面圖是截得它github主頁的示范案例:

仔細(xì)觀察后我們可以發(fā)現(xiàn),這個(gè)snabbdom官方案例中,核心內(nèi)容就是兩個(gè)函數(shù) -- h函數(shù) 和 patch函數(shù)

h函數(shù)

可以看到 h 函數(shù),有三個(gè)參數(shù)

  • 標(biāo)簽選擇器
  • 屬性
  • 子節(jié)點(diǎn)

比如說第一個(gè)h函數(shù)生成的vnode,就是一個(gè)div標(biāo)簽,綁定了click事件為someFn,第一個(gè)child為帶有style的span,sapn里是一個(gè)文本節(jié)點(diǎn)This is bold,第二個(gè)child就直接是一個(gè)文本節(jié)點(diǎn),第三個(gè)child就是一個(gè)帶有herf的a鏈接

patch函數(shù)

patch 分為兩種情況

  • 第一種是第一次渲染的時(shí)候 patch將vnode丟到container空容器中 var vnode = h('ul#list',{},[h('li.item',{},'大冰哥'),h('li.item',{},'倫哥'),h('li.item',{},'阿孔')])patch(container, vnode) // vnode 將 container 節(jié)點(diǎn)替換 復(fù)制代碼

第一次patch渲染的時(shí)候,是將生成的vnode往空容器里丟 可以對比之前的Jquery第一次渲染表格的時(shí)候,將table html append到容器中去

  • 第二種是更新節(jié)點(diǎn)的時(shí)候,newVnode將oldVnode替換btn.addEventListener('click',function() {var newVnode = h('ul#list',{},[h('li.item',{},'大冰哥'),h('li.item',{},'倫哥'),h('li.item',{},'孔祥宇'),h('li.item',{},'小老弟'),])patch(vnode, newVnode) }) 復(fù)制代碼

這里的patch就會(huì)將的vonde和之前的vnode進(jìn)行比對,只修改改動(dòng)的地方,沒動(dòng)的地方保持不變,這里的核心就是涉及的diff算法

我們可以清楚的看到,相對于之前的JQuery整個(gè)頁面dom全部替換的情況,用vdom的pathc函數(shù)只修改了我們相對老的vnode變動(dòng)的地方,沒改動(dòng)的地方就沒用動(dòng)(從頁面的閃爍可以看出來)

使用vdom重做之前Jq案例

vdom核心的api h函數(shù)和patch函數(shù)我們已經(jīng)有個(gè)基本的了解了,為了鞏固對其的認(rèn)識(shí),我們接下來用snabbdom重做我們之前的JQuery案例

直接先上代碼

<div id="container"></div><button id="btn-change">change</button><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-class.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-props.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-style.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-eventlisteners.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script><script>let container = document.getElementById('container')let btn = document.getElementById('btn-change')let snabbdom = window.snabbdomlet patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners])let h = snabbdom.hlet data = [{name: '張三',age: '20',address: '杭州'},{name: '李四',age: '22',address: '北京'},{name: '隔壁老王',age: '24',address: "西溪水岸"}]data.unshift({name: '姓名',age: '年齡',address: '地址'})let vnodefunction render(data) {// 創(chuàng)建虛擬table節(jié)點(diǎn) 第三個(gè)參數(shù),也就是虛擬table的孩子 應(yīng)該是虛擬的 行節(jié)點(diǎn)let newVnode = h('table', {}, data.map(item => {let tds = [] // 列,作為虛擬行的子項(xiàng)let ifor(i in item) {if (item.hasOwnProperty(i)) {tds.push(h('td', {}, item[i]+''))}}return h('tr', {}, tds) // 虛擬行節(jié)點(diǎn)的 孩子 應(yīng)該是虛擬的 列節(jié)點(diǎn)}))if (vnode) {patch(vnode,newVnode)} else {// 初次渲染patch(container,newVnode)}vnode = newVnode}btn.addEventListener('click', function(){data[1].age = 30,data[3].name = '一個(gè)女孩',render(data)})// 初始化時(shí)候渲染render(data)</script> </body> 復(fù)制代碼

代碼有點(diǎn)長,其實(shí)內(nèi)容還是我們之前講的,代碼主要干了下面的事情

  • 引入snabbdom核心文件,初始化h函數(shù)和patch函數(shù)
  • 第一次加載的時(shí)候render 其實(shí)本質(zhì)就是patch(container,newVnode)
  • 之后點(diǎn)擊change的時(shí)候,生成新的vnode,再patch(vnode,newVnode)

這里的render函數(shù)重點(diǎn)講解一下

  • newVnode生成的時(shí)候,第三個(gè)參數(shù)是childs
  • 而table的childs是行節(jié)點(diǎn)
  • tr行節(jié)點(diǎn)也是vnode,它再生成的時(shí)候也要使用h函數(shù),第三個(gè)參數(shù)是td列vnode
  • td列vnode的第三個(gè)參數(shù),就直接是文本節(jié)點(diǎn)啦,遍歷item的每一項(xiàng)push到tds數(shù)組中就可以了

到了這里,你對vdom應(yīng)該有個(gè)大體的認(rèn)識(shí)了,其實(shí),與其說vdom快,更準(zhǔn)確的說是相比于Jquer這種推翻dom的方式等,保證不慢而已。

總結(jié)

vdom的核心api

  • h('標(biāo)簽名', '屬性', [子元素])
  • h('標(biāo)簽名', '屬性', '文本')
  • patch(container, vnode)
  • patch(oldVnode,newVnode)

簡單介紹diff算法

什么是diff算法

我們在平時(shí)工作中,其實(shí)很多時(shí)候都會(huì)使用到diff算法

比如你在git提交代碼的時(shí)候使用的 git diff 命令,再或者是網(wǎng)上的一些代碼比對工具,而我們的虛擬dom,核心就是diff算法,我們前面講過,找出有必要更新的節(jié)點(diǎn)更新,沒有更新的節(jié)點(diǎn)就不要?jiǎng)印_@其中的核心就是如何找出哪些更新哪些不更新,這個(gè)過程就需要diff算法來完成

通過patch簡單講diff

我們趁熱打鐵,還是使用之前的snabbdom庫來簡單的講下diff算法的大體思路,在snabbdom中diff主要體現(xiàn)在patch中,我們接下來看一下patch的兩種情況 patch(container, vnode) 和 patch(vnode, newVnode)

篇幅有限,(其實(shí)是能力有限), 這里就簡單的講解,因?yàn)樯婕暗酵瓿傻膁iff算法的話東西實(shí)在是太多太多,有興趣的可以去看一下snabbdom的源碼

patch(container, vnode)

我們知道這個(gè)patch的過程是將一個(gè)vnode(vdom)添加到空容器生成真實(shí)dom的過程,主要的代碼流程如下:

function creatElement(vnode) {let tag = vnode.taglet attrs = vnode.attrs || {}let children = vnode.children || []// 無標(biāo)簽 直接跳出if (!tag) {return null}// 創(chuàng)建元素let elem = document.createElement(tag)// 添加屬性for(let attrName in attrs) {if (attrs.hasOwnProperty(attrName)) {elem.setAttribute(arrtName, arrts[attrName])}}// 遞歸創(chuàng)建子元素children.forEach((childVnode) => {elem.appendChild(createElement(childVnode))})return elem } 復(fù)制代碼

簡化后的代碼很簡單,大家也都能夠理解,其中的一個(gè)重要的點(diǎn)就是 自遞歸調(diào)用生成孩子節(jié)點(diǎn),終止條件就是tag為null的情況

patch(vnode, newVnode)

這個(gè)patch過程就是比較差異的過程,我們這里就只模擬最簡單的場景

第三個(gè)item改變,又新增第四個(gè)item

// 簡化流程 假設(shè)跟標(biāo)簽相同的兩個(gè)虛擬dom function updateChildren (vnode, newVnode) {let children = vnode.children || []let newChildren = newVnode.children || []// 遍歷現(xiàn)有的孩子children.forEach((oldChild, index) => {let newChild = newChildren[index]if (newChild === null) {return}// 兩者tag一樣,值得比較if (oldChild.tag === newChild.tag) {// 遞歸繼續(xù)比較子項(xiàng)updateChildren(oldchild, newChild)} else {// 兩者tag不一樣replaceNode(oldChild, newChild)}}) } 復(fù)制代碼

這里面的點(diǎn)就也遞歸,這里只是簡單的拿tag來判斷更新條件,其實(shí)實(shí)際的比這復(fù)雜很多很多; 而replace函數(shù)實(shí)際的操作就是將newVnode新生成的真實(shí)dom將老的dom替換掉,這里涉及更多的是原生dom操作,就不在贅述了。

到這里,基本的diff概念應(yīng)該大家有個(gè)認(rèn)識(shí)了,再次強(qiáng)調(diào),這里為了便于理解,將diff算法的流程簡化了很多,實(shí)際的diff算法的復(fù)雜程度遠(yuǎn)遠(yuǎn)高于以上這些,比如說

  • 節(jié)點(diǎn)的新增和刪除
  • 重新排序時(shí)以及這個(gè)過程的優(yōu)化
  • 節(jié)點(diǎn)屬性樣式事件等的變化
  • 還有怎么將算法優(yōu)化到極致等等。。

大家感興趣可以去深入了解。

總結(jié)

本文知識(shí)拋磚引玉,通過閱讀本文,讓不了解虛擬dom的同學(xué)對虛擬dom有一個(gè)很好的認(rèn)知,對diff算法有一個(gè)大體的認(rèn)識(shí)。能達(dá)到這個(gè)效果,我覺得這篇文章就很有價(jià)值了。想要深入了解虛擬dom或者diff算法的同學(xué)可以翻閱snabbdom的 patch.js的源碼,加深學(xué)習(xí)。

番外 Vue的key

寫文章的時(shí)候碰到有vue key綁定的問題,這里就借助這股熱勁,結(jié)合虛擬dom和diff算法,來了解一下Vue中的key

Vue 中的 key

首先Vue官網(wǎng)的解釋:

當(dāng) Vue.js 用 v-for 正在更新已渲染過的元素列表時(shí),它默認(rèn)用“就地復(fù)用”策略。如果數(shù)據(jù)項(xiàng)的順序被改變,Vue 將不會(huì)移動(dòng) DOM 元素來匹配數(shù)據(jù)項(xiàng)的順序, 而是簡單復(fù)用此處每個(gè)元素,并且確保它在特定索引下顯示已被渲染過的每個(gè)元素。

這里的就地復(fù)用的策略復(fù)用的是沒有發(fā)生改變的元素,其他的還要依次重排。

為了給 Vue 一個(gè)提示,以便它能跟蹤每個(gè)節(jié)點(diǎn)的身份,從而重用和重新排序現(xiàn)有元素,你需要為每項(xiàng)提供一個(gè)唯一 key 屬性。理想的 key 值是每項(xiàng)都有的唯一 id。

我們在使用的使用經(jīng)常會(huì)使用index(即數(shù)組的下標(biāo))來作為key,但其實(shí)這是不推薦的一種使用方法

如何理解,我們看下面一個(gè)例子:

這里有一個(gè)數(shù)組數(shù)據(jù)

const list = [{id: 1,name: 'test1',},{id: 2,name: 'test2',},{id: 3,name: 'test3',}, ] 復(fù)制代碼

我們現(xiàn)在想要在其后面追加一條數(shù)據(jù)

const list = [{id: 1,name: 'test1',},{id: 2,name: 'test2',},{id: 3,name: 'test3',},{id: 4,name: '添加到最后的一條數(shù)據(jù)',}, ] 復(fù)制代碼

這個(gè)時(shí)候用 index 作為 key, 是沒有問題的,因?yàn)閕ndex在后面累加了1

但是如果插入的數(shù)據(jù)是插在中間而不是最后,

const list = [{id: 1,name: 'test1',},{id: 4,name: '不甘落后跑到第二的的一條數(shù)據(jù)',}{id: 2,name: 'test2',},{id: 3,name: 'test3',}, ] 復(fù)制代碼

這個(gè)時(shí)候就會(huì)會(huì)出現(xiàn)一個(gè)情況:

之前的數(shù)據(jù) 之后的數(shù)據(jù)key: 0 index: 0 name: test1 key: 0 index: 0 name: test1 key: 1 index: 1 name: test2 key: 1 index: 1 name: 不甘落后跑到第二的的一條數(shù)據(jù) key: 2 index: 2 name: test3 key: 2 index: 2 name: test2key: 3 index: 3 name: test3 復(fù)制代碼

這樣一來,追加數(shù)據(jù)以后,除了第一條數(shù)據(jù)能夠就地復(fù)用,后三條都要重新渲染,這顯然不是我們想要的結(jié)果。

唯一key來改善:

這次我們把每一項(xiàng)的key 綁定成唯一標(biāo)示id

之前的數(shù)據(jù) 之后的數(shù)據(jù)key: 1 id: 1 index: 0 name: test1 key: 1 id: 1 index: 0 name: test1 key: 2 id: 2 index: 1 name: test2 key: 4 id: 4 index: 1 name: 不甘落后的一條數(shù)據(jù) key: 3 id: 3 index: 2 name: test3 key: 2 id: 2 index: 2 name: test2key: 3 id: 3 index: 3 name: test3 復(fù)制代碼

現(xiàn)在除了新增了id為4的不甘落后的數(shù)據(jù)是新加入的,其他的都復(fù)用了之前的dom,因?yàn)檫@里通過唯一key來進(jìn)行關(guān)聯(lián),不會(huì)隨著順序的改變而重新渲染。

所以我們需要使用key來給每個(gè)節(jié)點(diǎn)做一個(gè)唯一標(biāo)識(shí),Vue的Diff算法就可以正確的識(shí)別此節(jié)點(diǎn),找到正確的位置區(qū)插入新的節(jié)點(diǎn),所以一句話,key的作用主要是為了高效的更新虛擬DOM

靈魂畫手上線:

可以看到,當(dāng)我們老的數(shù)據(jù)轉(zhuǎn)為新的數(shù)據(jù)時(shí) [a,b,c,d] --> [a,e,b,c,d]

如果我們沒有使用一個(gè)正確的key,可能除了a數(shù)據(jù)可以復(fù)用以外,后面的四個(gè)數(shù)據(jù)都要重新渲染

而如果使用了一個(gè)正確的key的時(shí)候,就可以實(shí)現(xiàn)要更改的只有一處,也就是新增數(shù)據(jù) e,其他的就會(huì)如箭頭所示,繼續(xù)對應(yīng)復(fù)用。

總結(jié)

以上是生活随笔為你收集整理的虚拟DOM和Diff算法 - 入门级的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。