做diff_Virtual Dom amp;amp; Diff原理,极简版
前言
先介紹一個(gè)概念Virtual Dom,我猜大家或多或少都應(yīng)該知道什么是Virtual Dom吧,簡(jiǎn)單來說就是用js來模擬DOM中的節(jié)點(diǎn)。
Virtual Dom
下面就是一個(gè)Virtual Dom的結(jié)構(gòu),包含了標(biāo)簽名,擁有的屬性,孩子節(jié)點(diǎn),render函數(shù)
class Element {constructor(tagName, attrs, children) {this.tagName = tagName;this.attrs = attrs || {};this.children = children || [];}render () {//這個(gè)函數(shù)是用來生成真實(shí)DOM的,最后會(huì)把return的結(jié)果添加到頁(yè)面中去 } }創(chuàng)建一棵個(gè)Virtual Dom Tree && 渲染
/** <ul id="list"><li class="a">txt_a</li><li class="a">txt_b</li> </ul> **/ //根據(jù)上面結(jié)構(gòu)可以用一下方式創(chuàng)建一棵 Virtual Dom Tree let ul = Element('ul', { id: 'list' }, [Element('li', { class: 'a' }, ['txt_a']),Element('li', { class: 'b' }, ['txt_b']) ]);//ul 就是一棵個(gè)Virtual Dom Tree let ulDom = ul.render();//生成真實(shí)Dom document.body.appendChild(ulDom);//添加到頁(yè)面中以上就是Virtual Dom Tree如何被轉(zhuǎn)換成真實(shí)Dom并添加到網(wǎng)頁(yè)中的過程,再這個(gè)過程中我把render函數(shù)給省略,只是為了讓你們先了解原理,具體實(shí)現(xiàn)可以以后再深究。我學(xué)一個(gè)東西的時(shí)候,習(xí)慣是先把整體原理弄清楚,再去深入學(xué)習(xí)相關(guān)的知識(shí)。
Diff 算法
在介紹Diff算法之前,再次聲明我只會(huì)列舉Diff算法中會(huì)用到的函數(shù),并串聯(lián)它們之間的關(guān)系并不會(huì)給出具體實(shí)現(xiàn)的代碼
介紹
diff算法是進(jìn)行虛擬節(jié)點(diǎn)Element的對(duì)比,并返回一個(gè)patchs對(duì)象,用來存儲(chǔ)兩個(gè)節(jié)點(diǎn)不同的地方,最后用patchs記錄的消息去局部更新Dom。
兩個(gè)樹如果完全比較的話需要時(shí)間復(fù)雜度為O(n^3),如果對(duì)O(n^3)不太清楚的話建議去網(wǎng)上搜索資料。而在Diff算法中因?yàn)榭紤]效率的問題,只會(huì)對(duì)同層級(jí)元素比較,時(shí)間復(fù)雜度則為O(n),說白了就是深度遍歷,并比較同層級(jí)的節(jié)點(diǎn)。
Diff只需下面兩句代碼
- 判斷兩棵Virtual Dom Tree 差異
- 把差異更新到真實(shí)Dom中去
Diff中所用到的函數(shù)
//深度遍歷樹,將需要做變更操作的取出來 //局部更新 DOM function patch(node,patchs){//代碼略 }// diff 入口,比較新舊兩棵樹的差異 function diff (oldTree, newTree) {let index = 0let patches = {} // 記錄每個(gè)節(jié)點(diǎn)差異的補(bǔ)丁dfs(oldTree, newTree, index, patches)return patches; } /*** dfs 深度遍歷查找節(jié)點(diǎn)差異* @param oldNode - 舊虛擬Dom樹* @param newNode - 新虛擬Dom樹* @param index - 當(dāng)前所在樹的第幾層* @param patches - 記錄節(jié)點(diǎn)差異*/ function dfs (oldNode, newNode, index, patches){let currentPatch = [];//當(dāng)前層的差異對(duì)比if (!newNode) {//如果節(jié)點(diǎn)不存不用處理,listdiff函數(shù)會(huì)處理被刪除的節(jié)點(diǎn)}else if (isTxt(oldNode) && isTxt(newNode)) {//isTxt用來判斷是否是文本,為了簡(jiǎn)便這邊并沒有聲明if (newNode !== oldNode) currentPatch.push({ type: "text", content: newNode })//如果發(fā)現(xiàn)文本不同,currentPatch會(huì)記錄一個(gè)差異 }else if(oldNode.tagName === newNode.tagName && oldNode.key === newNode.key){//如果發(fā)現(xiàn)兩個(gè)節(jié)點(diǎn)一樣 則去判斷節(jié)點(diǎn)是屬性是否一樣,并記錄下來let attrsPatches = diffAttrs(oldNode, newNode)if (attrsPatches) {//有屬性差異則把差異記錄下來currentPatch.push({ type: "attrs", "attrs": attrsPatches })}// 遞歸遍歷子節(jié)點(diǎn),并對(duì)子節(jié)點(diǎn)進(jìn)行diff比較diffChildren(oldNode.children, newNode.children, index, patches)}else{//最后一種情況是,兩個(gè)節(jié)點(diǎn)完全不一樣,這樣只需要把舊節(jié)點(diǎn)之間替換就行//把當(dāng)前差異記錄下來currentPatch.push({ type: "replace", node: newNode})}//如果有差異則記錄到當(dāng)前層去if (currentPatch.length) {if (patches[index]) {patches[index] = patches[index].concat(currentPatch)} else {patches[index] = currentPatch}} } //判斷兩個(gè)節(jié)點(diǎn)的屬性差異 function diffAttrs(oldNode, newNode){let attrsPatches = {};//記錄差異let count = 0;//記錄差異的條數(shù)/**代碼略判斷兩個(gè)節(jié)點(diǎn)的屬性差異的代碼就略了,讓你們知道這里的代碼就是判斷兩個(gè)節(jié)點(diǎn)的屬性有哪些差異,如果有差異就記錄在attrsPatches這個(gè)對(duì)象中,并把它返回**/if(0 == count){return null;}else {return attrsPatches; } } //判斷孩子節(jié)點(diǎn) function diffChildren(oldChild, newChild, index, patches){let { changes, list } = listDiff(oldChild, newChild, index, patches);if (changes.length) {//如果有差異則記錄到當(dāng)前層去if (patches[index]) {patches[index] = patches[index].concat(changes);} else {patches[index] = changes;}}// 代碼略//遍歷當(dāng)前數(shù)組oldChild && oldChild.forEach((item, i) => {// 代碼略let node;// 經(jīng)過判斷后node節(jié)點(diǎn)是同時(shí)存在于oldChild 和 newChild中//則對(duì)節(jié)點(diǎn)進(jìn)行遞歸遍歷 相當(dāng)于 進(jìn)入下一層 節(jié)點(diǎn),let curIndex;dfs(item, node, curIndex, patches);// 代碼略})}//判斷oldNodeList, newNodeList 節(jié)點(diǎn)的位置差,主要是為了判斷哪些節(jié)點(diǎn)被移動(dòng)、刪除、新增。 function listDiff(oldNodeList, newNodeList, index){let changes = [];//記錄 oldNodeList, newNodeList節(jié)點(diǎn)的位置差異,是被移動(dòng)、刪除、新增let list = [];//記錄 oldNodeList,newNodeList 同時(shí)存在的節(jié)點(diǎn)/**具體判斷邏輯的代碼就略了**/return {changes,list}; }如果大家對(duì)函數(shù)之間的調(diào)用還不明白的話可以看下面的圖
最后
Virtual Dom 算法的實(shí)現(xiàn)也就是以下三步
- 通過 JS 來模擬生成 Virtual Dom Tree
- 判斷兩個(gè) Tree 的差異
- 渲染差異
上面省略了很多代碼,主要是為了讓大家快速了解Dom diff 的基本原理和流程,如果想更深入的了解,可以在網(wǎng)上查閱相關(guān)資料。
總結(jié)
以上是生活随笔為你收集整理的做diff_Virtual Dom amp;amp; Diff原理,极简版的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dll 导出函数 下划线_内核中的代码完
- 下一篇: ios 重复引用 静态库_iOS 解决一