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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

合格前端系列第五弹- Virtual Dom Diff

發(fā)布時間:2025/3/20 HTML 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 合格前端系列第五弹- Virtual Dom Diff 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業(yè)重金招聘Python工程師標準>>>

前言

這是一篇很長的文章!!!堅持看到最后有彩蛋哦!!!

文章開篇,我們先思考一個問題,大家都說 virtual dom 這,virtual dom 那的,那么 virtual dom 到底是啥?

首先,我們得明確一點,所謂的 virtual dom,也就是虛擬節(jié)點。它通過 JS 的 Object 對象模擬 DOM 中的節(jié)點,然后再通過特定的 render 方法將其渲染成真實的 DOM 節(jié)點。

其次我們還得知道一點,那就是 virtual dom 做的一件事情到底是啥。我們知道的對于頁面的重新渲染一般的做法是通過操作 dom,重置 innerHTML 去完成這樣一件事情。而 virtual dom 則是通過 JS 層面的計算,返回一個 patch 對象,即補丁對象,在通過特定的操作解析 patch 對象,完成頁面的重新渲染。具體 virtual dom 渲染的一個流程如圖所示

接下來,我會老規(guī)矩,邊上代碼,邊解析,帶著小伙伴們一起實現一個virtual dom && diff。具體步驟如下

  • 實現一個 utils 方法庫
  • 實現一個 Element(virtual dom)
  • 實現 diff 算法
  • 實現 patch
  • 一、實現一個 utils 方法庫

    俗話說的好,磨刀不廢砍柴功,為了后面的方便,我會在這先帶著大家實現后面經常用到的一些方法,畢竟要是每次都寫一遍用的方法,豈不得瘋,因為代碼簡單,所以這里我就直接貼上代碼了

    const _ = exports_.setAttr = function setAttr (node, key, value) {switch (key) {case 'style':node.style.cssText = valuebreak;case 'value':let tagName = node.tagName || ''tagName = tagName.toLowerCase()if (tagName === 'input' || tagName === 'textarea') {node.value = value} else {// 如果節(jié)點不是 input 或者 textarea, 則使用 `setAttribute` 去設置屬性node.setAttribute(key, value)}break;default:node.setAttribute(key, value)break;} }_.slice = function slice (arrayLike, index) {return Array.prototype.slice.call(arrayLike, index) }_.type = function type (obj) {return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g, '') }_.isArray = function isArray (list) {return _.type(list) === 'Array' }_.toArray = function toArray (listLike) {if (!listLike) return []let list = []for (let i = 0, l = listLike.length; i < l; i++) {list.push(listLike[i])}return list }_.isString = function isString (list) {return _.type(list) === 'String' }_.isElementNode = function (node) {return node.nodeType === 1 }

    二、實現一個 Element

    這里我們需要做的一件事情很 easy ,那就是實現一個 Object 去模擬 DOM 節(jié)點的展示形式。真實節(jié)點如下

    <ul id="list"><li class="item">item1</li><li class="item">item2</li><li class="item">item3</li> </ul>

    我們需要完成一個 Element 模擬上面的真實節(jié)點,形式如下

    let ul = {tagName: 'ul',attrs: {id: 'list'},children: [{ tagName: 'li', attrs: { class: 'item' }, children: ['item1'] },{ tagName: 'li', attrs: { class: 'item' }, children: ['item1'] },{ tagName: 'li', attrs: { class: 'item' }, children: ['item1'] },] }

    看到這里,我們可以看到的是 el 對象中的 tagName,attrs,children 都可以提取出來到 Element 中去,即

    class Element {constructor(tagName, attrs, children) {this.tagName = tagNamethis.attrs = attrsthis.children = children} } function el (tagName, attrs, children) {return new Element(tagName, attrs, children) } module.exports = el;

    那么上面的ul就可以用更簡化的方式進行書寫了,即

    let ul = el('ul', { id: 'list' }, [el('li', { class: 'item' }, ['Item 1']),el('li', { class: 'item' }, ['Item 2']),el('li', { class: 'item' }, ['Item 3']) ])

    ul 則是 Element 對象,如圖

    OK,到這我們 Element 算是實現一半,剩下的一般則是提供一個 render 函數,將 Element 對象渲染成真實的 DOM 節(jié)點。完整的 Element 的代碼如下

    import _ from './utils'/*** @class Element Virtrual Dom* @param { String } tagName* @param { Object } attrs Element's attrs, 如: { id: 'list' }* @param { Array <Element|String> } 可以是Element對象,也可以只是字符串,即textNode*/ class Element {constructor(tagName, attrs, children) {// 如果只有兩個參數if (_.isArray(attrs)) {children = attrsattrs = {}}this.tagName = tagNamethis.attrs = attrs || {}this.children = children// 設置this.key屬性,為了后面list diff做準備this.key = attrs? attrs.key: void 0}render () {let el = document.createElement(this.tagName)let attrs = this.attrsfor (let attrName in attrs) { // 設置節(jié)點的DOM屬性let attrValue = attrs[attrName]_.setAttr(el, attrName, attrValue)}let children = this.children || []children.forEach(child => {let childEl = child instanceof Element? child.render() // 若子節(jié)點也是虛擬節(jié)點,遞歸進行構建: document.createTextNode(child) // 若是字符串,直接構建文本節(jié)點el.appendChild(childEl)})return el} } function el (tagName, attrs, children) {return new Element(tagName, attrs, children) } module.exports = el;

    這個時候我們執(zhí)行寫好的 render 方法,將 Element 對象渲染成真實的節(jié)點

    let ulRoot = ul.render() document.body.appendChild(ulRoot);

    效果如圖

    至此,我們的 Element 便得以實現了。

    三、實現 diff 算法

    這里我們做的就是實現一個 diff 算法進行虛擬節(jié)點 Element 的對比,并返回一個 patch 對象,用來存儲兩個節(jié)點不同的地方。這也是整個 virtual dom 實現最核心的一步。而 diff 算法又包含了兩個不一樣的算法,一個是 O(n),一個則是 O(max(m, n))

    1、同層級元素比較(O(n))

    首先,我們的知道的是,如果元素之間進行完全的一個比較,即新舊 Element 對象的父元素,本身,子元素之間進行一個混雜的比較,其實現的時間復雜度為 O(n^3)。但是在我們前端開發(fā)中,很少會出現跨層級處理節(jié)點,所以這里我們會做一個同級元素之間的一個比較,則其時間復雜度則為 O(n)。算法流程如圖所示

    在這里,我們做同級元素比較時,可能會出現四種情況

    • 整個元素都不一樣,即元素被 replace 掉
    • 元素的 attrs 不一樣
    • 元素的 text 文本不一樣
    • 元素順序被替換,即元素需要 reorder

    上面列舉第四種情況屬于 diff 的第二種算法,這里我們先不討論,我們在后面再進行詳細的討論
    針對以上四種情況,我們先設置四個常量進行表示。diff 入口方法及四種狀態(tài)如下

    const REPLACE = 0 // replace => 0 const ATTRS = 1 // attrs => 1 const TEXT = 2 // text => 2 const REORDER = 3 // reorder => 3// diff 入口,比較新舊兩棵樹的差異 function diff (oldTree, newTree) {let index = 0let patches = {} // 用來記錄每個節(jié)點差異的補丁對象walk(oldTree, newTree, index, patches)return patches }

    OK,狀態(tài)定義好了,接下來開搞。我們一個一個實現,獲取到每個狀態(tài)的不同。這里需要注意的一點就是,我們這里的 diff 比較只會和上面的流程圖顯示的一樣,只會兩兩之間進行比較,如果有節(jié)點 remove 掉,這里會 pass 掉,直接走 list diff。

    a、首先我們先從最頂層的元素依次往下進行比較,直到最后一層元素結束,并把每個層級的差異存到 patch 對象中去,即實現walk方法

    /*** walk 遍歷查找節(jié)點差異* @param { Object } oldNode* @param { Object } newNode* @param { Number } index - currentNodeIndex* @param { Object } patches - 記錄節(jié)點差異的對象*/ function walk (oldNode, newNode, index, patches) {let currentPatch = []// 如果oldNode被remove掉了if (newNode === null || newNode === undefined) {// 先不做操作, 具體交給 list diff 處理}// 比較文本之間的不同else if (_.isString(oldNode) && _.isString(newNode)) {if (newNode !== oldNode) currentPatch.push({ type: TEXT, content: newNode })}// 比較attrs的不同else if (oldNode.tagName === newNode.tagName &&oldNode.key === newNode.key) {let attrsPatches = diffAttrs(oldNode, newNode)if (attrsPatches) {currentPatch.push({ type: ATTRS, attrs: attrsPatches })}// 遞歸進行子節(jié)點的diff比較diffChildren(oldNode.children, newNode.children, index, patches)}else {currentPatch.push({ type: REPLACE, node: newNode})}if (currentPatch.length) {patches[index] = currentPatch} }function diffAttrs (oldNode, newNode) {let count = 0let oldAttrs = oldNode.attrslet newAttrs = newNode.attrslet key, valuelet attrsPatches = {}// 如果存在不同的 attrsfor (key in oldAttrs) {value = oldAttrs[key]// 如果 oldAttrs 移除掉一些 attrs, newAttrs[key] === undefinedif (newAttrs[key] !== value) {count++attrsPatches[key] = newAttrs[key]}}// 如果存在新的 attrfor (key in newAttrs) {value = newAttrs[key]if (!oldAttrs.hasOwnProperty(key)) {count++attrsPatches[key] = value}}if (count === 0) {return null}return attrsPatches }

    b、實際上我們需要對新舊元素進行一個深度的遍歷,為每個節(jié)點加上一個唯一的標記,具體流程如圖所示

    如上圖,我們接下來要做的一件事情就很明確了,那就是在做深度遍歷比較差異的時候,將每個元素節(jié)點,標記上一個唯一的標識。具體做法如下

    // 設置節(jié)點唯一標識 let key_id = 0 // diff with children function diffChildren (oldChildren, newChildren, index, patches) {// 存放當前node的標識,初始化值為 0let currentNodeIndex = indexoldChildren.forEach((child, i) => {key_id++let newChild = newChildren[i]currentNodeIndex = key_id// 遞歸繼續(xù)比較walk(child, newChild, currentNodeIndex, patches)}) }

    OK,這一步偶了。咱調用一下看下效果,看看兩個不同的 Element 對象比較會返回一個哪種形式的 patch 對象

    let ul = el('ul', { id: 'list' }, [el('li', { class: 'item' }, ['Item 1']),el('li', { class: 'item' }, ['Item 2']) ]) let ul1 = el('ul', { id: 'list1' }, [el('li', { class: 'item1' }, ['Item 4']),el('li', { class: 'item2' }, ['Item 5']) ]) let patches = diff(ul, ul1); console.log(patches);

    控制臺結果如圖

    完整的 diff 代碼如下(包含了調用 list diff 的方法,如果你在跟著文章踩坑的話,把里面一些代碼注釋掉即可)

    import _ from './utils' import listDiff from './list-diff'const REPLACE = 0 const ATTRS = 1 const TEXT = 2 const REORDER = 3// diff 入口,比較新舊兩棵樹的差異 function diff (oldTree, newTree) {let index = 0let patches = {} // 用來記錄每個節(jié)點差異的補丁對象walk(oldTree, newTree, index, patches)return patches }/*** walk 遍歷查找節(jié)點差異* @param { Object } oldNode* @param { Object } newNode* @param { Number } index - currentNodeIndex* @param { Object } patches - 記錄節(jié)點差異的對象*/ function walk (oldNode, newNode, index, patches) {let currentPatch = []// 如果oldNode被remove掉了,即 newNode === null的時候if (newNode === null || newNode === undefined) {// 先不做操作, 具體交給 list diff 處理}// 比較文本之間的不同else if (_.isString(oldNode) && _.isString(newNode)) {if (newNode !== oldNode) currentPatch.push({ type: TEXT, content: newNode })}// 比較attrs的不同else if (oldNode.tagName === newNode.tagName &&oldNode.key === newNode.key) {let attrsPatches = diffAttrs(oldNode, newNode)if (attrsPatches) {currentPatch.push({ type: ATTRS, attrs: attrsPatches })}// 遞歸進行子節(jié)點的diff比較diffChildren(oldNode.children, newNode.children, index, patches, currentPatch)}else {currentPatch.push({ type: REPLACE, node: newNode})}if (currentPatch.length) {patches[index] = currentPatch} }function diffAttrs (oldNode, newNode) {let count = 0let oldAttrs = oldNode.attrslet newAttrs = newNode.attrslet key, valuelet attrsPatches = {}// 如果存在不同的 attrsfor (key in oldAttrs) {value = oldAttrs[key]// 如果 oldAttrs 移除掉一些 attrs, newAttrs[key] === undefinedif (newAttrs[key] !== value) {count++attrsPatches[key] = newAttrs[key]}}// 如果存在新的 attrfor (key in newAttrs) {value = newAttrs[key]if (!oldAttrs.hasOwnProperty(key)) {attrsPatches[key] = value}}if (count === 0) {return null}return attrsPatches }// 設置節(jié)點唯一標識 let key_id = 0 // diff with children function diffChildren (oldChildren, newChildren, index, patches, currentPatch) {let diffs = listDiff(oldChildren, newChildren, 'key')newChildren = diffs.childrenif (diffs.moves.length) {let reorderPatch = { type: REORDER, moves: diffs.moves }currentPatch.push(reorderPatch)}// 存放當前node的標識,初始化值為 0let currentNodeIndex = indexoldChildren.forEach((child, i) => {key_id++let newChild = newChildren[i]currentNodeIndex = key_id// 遞歸繼續(xù)比較walk(child, newChild, currentNodeIndex, patches)}) }module.exports = diff

    看到這里的小伙伴們,如果覺得只看到 patch 對象而看不到 patch 解析后頁面重新渲染的操作而覺得比較無聊的話,可以先跳過 list diff 這一章節(jié),直接跟著 patch 方法實現那一章節(jié)進行強懟,可能會比較帶勁吧!也希望小伙伴們可以和我達成共識(因為我自己原來好像也是這樣干的)。

    2、listDiff實現 O(m*n) => O(max(m, n))

    首先我們得明確一下為什么需要 list diff 這種算法的存在,list diff 做的一件事情是怎樣的,然后它又是如何做到這么一件事情的。

    舉個栗子,我有新舊兩個 Element 對象,分別為

    let oldTree = el('ul', { id: 'list' }, [el('li', { class: 'item1' }, ['Item 1']),el('li', { class: 'item2' }, ['Item 2']),el('li', { class: 'item3' }, ['Item 3']) ]) let newTree = el('ul', { id: 'list' }, [el('li', { class: 'item3' }, ['Item 3']),el('li', { class: 'item1' }, ['Item 1']),el('li', { class: 'item2' }, ['Item 2']) ])

    如果要進行 diff 比較的話,我們直接用上面的方法就能比較出來,但我們可以看出來這里只做了一次節(jié)點的 move。如果直接按照上面的 diff 進行比較,并且通過后面的 patch 方法進行 patch 對象的解析渲染,那么將需要操作三次 DOM 節(jié)點才能完成視圖最后的 update。

    當然,如果只有三個節(jié)點的話那還好,我們的瀏覽器還能吃的消,看不出啥性能上的區(qū)別。那么問題來了,如果有 N 多節(jié)點,并且這些節(jié)點只是做了一小部分 remove,insert,move 的操作,那么如果我們還是按照一一對應的 DOM 操作進行 DOM 的重新渲染,那豈不是操作太昂貴?

    所以,才會衍生出 list diff 這種算法,專門進行負責收集 remove,insert,move 操作,當然對于這個操作我們需要提前在節(jié)點的 attrs 里面申明一個 DOM 屬性,表示該節(jié)點的唯一性。另外上張圖說明一下 list diff 的時間復雜度,小伙伴們可以看圖了解一下

    OK,接下來我們舉個具體的例子說明一下 list diff 具體如何進行操作的,代碼如下

    let oldTree = el('ul', { id: 'list' }, [el('li', { key: 1 }, ['Item 1']),el('li', {}, ['Item']),el('li', { key: 2 }, ['Item 2']),el('li', { key: 3 }, ['Item 3']) ]) let newTree = el('ul', { id: 'list' }, [el('li', { key: 3 }, ['Item 3']),el('li', { key: 1 }, ['Item 1']),el('li', {}, ['Item']),el('li', { key: 4 }, ['Item 4']) ])

    對于上面例子中的新舊節(jié)點的差異對比,如果我說直接讓小伙伴們看代碼捋清楚節(jié)點操作的流程,估計大家都會說我耍流氓。所以我整理了一幅流程圖,解釋了 list diff 具體如何進行計算節(jié)點差異的,如下

    我們看圖說話,list diff 做的事情就很簡單明了啦。

    • 第一步,newChildren 向 oldChildren 的形式靠近進行操作(移動操作,代碼中做法是直接遍歷 oldChildren 進行操作),得到 simulateChildren = [key1, 無key, null, key3]
      step1. oldChildren 第一個元素 key1 對應 newChildren 中的第二個元素
      step2. oldChildren 第二個元素 無key 對應 newChildren 中的第三個元素
      step3. oldChildren 第三個元素 key2 在 newChildren 中找不到,直接設為 null step4. oldChildren 第四個元素 key3 對應 newChildren 中的第一個元素
    • 第二步,稍微處理一下得出的 simulateChildren,將 null 元素以及 newChildren 中的新元素加入,得到 simulateChildren = [key1, 無key, key3, key4]
    • 第三步,將得出的 simulateChildren 向 newChildren 的形式靠近,并將這里的移動操作全部記錄下來(注:元素的 move 操作這里會當成 remove 和 insert 操作的結合)。所以最后我們得出上圖中的一個 moves 數組,存儲了所有節(jié)點移動類的操作。

    OK,整體流程我們捋清楚了,接下來要做的事情就會簡單很多了。我們只需要用代碼把上面列出來要做的事情得以實現即可。(注:這里本來我是想分步驟一步一步實現,但是每一步牽扯到的東西有點多,怕到時貼出來的代碼太多,我還是直接把 list diff 所有代碼寫上注釋貼上吧)

    /*** Diff two list in O(N).* @param {Array} oldList - 原始列表* @param {Array} newList - 經過一些操作的得出的新列表* @return {Object} - {moves: <Array>}* - moves list操作記錄的集合*/ function diff (oldList, newList, key) {let oldMap = getKeyIndexAndFree(oldList, key)let newMap = getKeyIndexAndFree(newList, key)let newFree = newMap.freelet oldKeyIndex = oldMap.keyIndexlet newKeyIndex = newMap.keyIndex// 記錄所有move操作let moves = []// a simulate listlet children = []let i = 0let itemlet itemKeylet freeIndex = 0// newList 向 oldList 的形式靠近進行操作while (i < oldList.length) {item = oldList[i]itemKey = getItemKey(item, key)if (itemKey) {if (!newKeyIndex.hasOwnProperty(itemKey)) {children.push(null)} else {let newItemIndex = newKeyIndex[itemKey]children.push(newList[newItemIndex])}} else {let freeItem = newFree[freeIndex++]children.push(freeItem || null)}i++}let simulateList = children.slice(0)// 移除列表中一些不存在的元素i = 0while (i < simulateList.length) {if (simulateList[i] === null) {remove(i)removeSimulate(i)} else {i++}}// i => new list// j => simulateListlet j = i = 0while (i < newList.length) {item = newList[i]itemKey = getItemKey(item, key)let simulateItem = simulateList[j]let simulateItemKey = getItemKey(simulateItem, key)if (simulateItem) {if (itemKey === simulateItemKey) {j++}else {// 如果移除掉當前的 simulateItem 可以讓 item在一個正確的位置,那么直接移除let nextItemKey = getItemKey(simulateList[j + 1], key)if (nextItemKey === itemKey) {remove(i)removeSimulate(j)j++ // 移除后,當前j的值是正確的,直接自加進入下一循環(huán)} else {// 否則直接將item 執(zhí)行 insertinsert(i, item)}}// 如果是新的 item, 直接執(zhí)行 inesrt} else {insert(i, item)}i++}// if j is not remove to the end, remove all the rest item// let k = 0;// while (j++ < simulateList.length) {// remove(k + i);// k++;// }// 記錄remove操作function remove (index) {let move = {index: index, type: 0}moves.push(move)}// 記錄insert操作function insert (index, item) {let move = {index: index, item: item, type: 1}moves.push(move)}// 移除simulateList中對應實際list中remove掉節(jié)點的元素function removeSimulate (index) {simulateList.splice(index, 1)}// 返回所有操作記錄return {moves: moves,children: children} } /*** 將 list轉變成 key-item keyIndex 對象的形式進行展示.* @param {Array} list* @param {String|Function} key*/ function getKeyIndexAndFree (list, key) {let keyIndex = {}let free = []for (let i = 0, len = list.length; i < len; i++) {let item = list[i]let itemKey = getItemKey(item, key)if (itemKey) {keyIndex[itemKey] = i} else {free.push(item)}}// 返回 key-item keyIndexreturn {keyIndex: keyIndex,free: free} }function getItemKey (item, key) {if (!item || !key) return void 0return typeof key === 'string'? item[key]: key(item) }module.exports = diff

    四、實現 patch,解析 patch 對象

    相信還是有不少小伙伴會直接從前面的章節(jié)跳過來,為了看到 diff 后頁面的重新渲染。

    如果你是仔仔細細看完了 diff 同層級元素比較之后過來的,那么其實這里的操作還是蠻簡單的。因為他和前面的操作思路基本一致,前面是遍歷 Element,給其唯一的標識,那么這里則是順著 patch 對象提供的唯一的鍵值進行解析的。直接給大家上一些深度遍歷的代碼

    function patch (rootNode, patches) {let walker = { index: 0 }walk(rootNode, walker, patches) }function walk (node, walker, patches) {let currentPatches = patches[walker.index] // 從patches取出當前節(jié)點的差異let len = node.childNodes? node.childNodes.length: 0for (let i = 0; i < len; i++) { // 深度遍歷子節(jié)點let child = node.childNodes[i]walker.index++walk(child, walker, patches)}if (currentPatches) {dealPatches(node, currentPatches) // 對當前節(jié)點進行DOM操作} }

    歷史總是驚人的相似,現在小伙伴應該知道之前深度遍歷給 Element 每個節(jié)點加上唯一標識的好處了吧。OK,接下來我們根據不同類型的差異對當前節(jié)點進行操作

    function dealPatches (node, currentPatches) {currentPatches.forEach(currentPatch => {switch (currentPatch.type) {case REPLACE:let newNode = (typeof currentPatch.node === 'string')? document.createTextNode(currentPatch.node): currentPatch.node.render()node.parentNode.replaceChild(newNode, node)breakcase REORDER:reorderChildren(node, currentPatch.moves)breakcase ATTRS:setProps(node, currentPatch.props)breakcase TEXT:if (node.textContent) {node.textContent = currentPatch.content} else {// for ienode.nodeValue = currentPatch.content}breakdefault:throw new Error('Unknown patch type ' + currentPatch.type)}}) }

    具體的 setAttrs 和 reorder 的實現如下

    function setAttrs (node, props) {for (let key in props) {if (props[key] === void 0) {node.removeAttribute(key)} else {let value = props[key]_.setAttr(node, key, value)}} } function reorderChildren (node, moves) {let staticNodeList = _.toArray(node.childNodes)let maps = {} // 存儲含有key特殊字段的節(jié)點staticNodeList.forEach(node => {// 如果當前節(jié)點是ElementNode,通過maps將含有key字段的節(jié)點進行存儲if (_.isElementNode(node)) {let key = node.getAttribute('key')if (key) {maps[key] = node}}})moves.forEach(move => {let index = move.indexif (move.type === 0) { // remove itemif (staticNodeList[index] === node.childNodes[index]) { // maybe have been removed for insertingnode.removeChild(node.childNodes[index])}staticNodeList.splice(index, 1)} else if (move.type === 1) { // insert itemlet insertNode = maps[move.item.key]? maps[move.item.key] // reuse old item: (typeof move.item === 'object')? move.item.render(): document.createTextNode(move.item)staticNodeList.splice(index, 0, insertNode)node.insertBefore(insertNode, node.childNodes[index] || null)}}) }

    到這,我們的 patch 方法也得以實現了,virtual dom && diff 也算完成了,終于可以松一口氣了。能夠看到這里的小伙伴們,給你們一個大大的贊。

    總結

    文章先從 Element 模擬 DOM 節(jié)點開始,然后通過 render 方法將 Element 還原成真實的 DOM 節(jié)點。然后再通過完成 diff 算法,比較新舊 Element 的不同,并記錄在 patch 對象中。最后在完成 patch 方法,將 patch 對象解析,從而完成 DOM 的 update。

    以上所有代碼在我 github 的 overwrite 項目里面都有。

    喜歡的小伙伴可以動動小手點一下 star 按鈕

    QQ討論群-前端大雜燴:731175396

    最后送小伙伴一句名言

    轉載于:https://my.oschina.net/qiangdada/blog/975591

    與50位技術專家面對面20年技術見證,附贈技術全景圖

    總結

    以上是生活随笔為你收集整理的合格前端系列第五弹- Virtual Dom Diff的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。