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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

textarea实现datalist效果_手把手撸代码实现Virtual Dom amp;amp; Diff

發(fā)布時間:2025/3/20 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 textarea实现datalist效果_手把手撸代码实现Virtual Dom amp;amp; Diff 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

文章開篇,我們先思考一個問題,大家都說 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ī)矩,邊上代碼,邊解析,帶著小伙伴們一起實現(xiàn)一個virtual dom && diff。具體步驟如下

  • 實現(xiàn)一個 utils 方法庫

  • 實現(xiàn)一個 Element(virtual dom)

  • 實現(xiàn) diff 算法

  • 實現(xiàn) patch

  • 一、實現(xiàn)一個 utils 方法庫

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

    const?_?=?exports

    _.setAttr?=?function?setAttr?(node,?key,?value)?{
    ??switch?(key)?{
    ????case?'style':
    ??????node.style.cssText?=?value
    ??????break;
    ????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?????list.push(listLike[i])
    ??}
    ??return?list
    }

    _.isString?=?function?isString?(list)?{
    ??return?_.type(list)?===?'String'
    }

    _.isElementNode?=?function?(node)?{
    ??return?node.nodeType?===?1
    }

    二、實現(xiàn)一個 Element

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

    <ul?id="list">
    ??<li?class="item">item1li>
    ??<li?class="item">item2li>
    ??<li?class="item">item3li>
    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??=?tagName
    ????this.attrs????=?attrs
    ????this.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 算是實現(xiàn)一半,剩下的一般則是提供一個 render 函數(shù),將 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對象,也可以只是字符串,即textNode
    ?*/
    class?Element?{
    ??constructor(tagName,?attrs,?children)?{
    ????//?如果只有兩個參數(shù)
    ????if?(_.isArray(attrs))?{
    ??????children?=?attrs
    ??????attrs?=?{}
    ????}

    ????this.tagName??=?tagName
    ????this.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.attrs

    ????for?(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é)點,遞歸進行構(gòu)建
    ????????:?document.createTextNode(child)??//?若是字符串,直接構(gòu)建文本節(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 便得以實現(xiàn)了。

    三、實現(xiàn) diff 算法

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

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

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

    在這里,我們做同級元素比較時,可能會出現(xià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???=?0
    ??let?patches?=?{}?//?用來記錄每個節(jié)點差異的補丁對象
    ??walk(oldTree,?newTree,?index,?patches)
    ??return?patches
    }

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

    a、首先我們先從最頂層的元素依次往下進行比較,直到最后一層元素結(jié)束,并把每個層級的差異存到 patch 對象中去,即實現(xiàn)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????=?0
    ??let?oldAttrs?=?oldNode.attrs
    ??let?newAttrs?=?newNode.attrs

    ??let?key,?value
    ??let?attrsPatches?=?{}

    ??//?如果存在不同的?attrs
    ??for?(key?in?oldAttrs)?{
    ????value?=?oldAttrs[key]
    ????//?如果?oldAttrs?移除掉一些?attrs,?newAttrs[key]?===?undefined
    ????if?(newAttrs[key]?!==?value)?{
    ??????count++
    ??????attrsPatches[key]?=?newAttrs[key]
    ????}
    ??}
    ??//?如果存在新的?attr
    ??for?(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的標識,初始化值為?0
    ??let?currentNodeIndex?=?index

    ??oldChildren.forEach((child,?i)?=>?{
    ????key_id++
    ????let?newChild?=?newChildren[i]
    ????currentNodeIndex?=?key_id

    ????//?遞歸繼續(xù)比較
    ????walk(child,?newChild,?currentNodeIndex,?patches)
    ??})
    }

    OK,這一步偶了。咱調(diào)用一下看下效果,看看兩個不同的 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);

    控制臺結(jié)果如圖 ?

    完整的 diff 代碼如下(包含了調(diào)用 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???=?0
    ??let?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????=?0
    ??let?oldAttrs?=?oldNode.attrs
    ??let?newAttrs?=?newNode.attrs

    ??let?key,?value
    ??let?attrsPatches?=?{}

    ??//?如果存在不同的?attrs
    ??for?(key?in?oldAttrs)?{
    ????value?=?oldAttrs[key]
    ????//?如果?oldAttrs?移除掉一些?attrs,?newAttrs[key]?===?undefined
    ????if?(newAttrs[key]?!==?value)?{
    ??????count++
    ??????attrsPatches[key]?=?newAttrs[key]
    ????}
    ??}
    ??//?如果存在新的?attr
    ??for?(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.children

    ??if?(diffs.moves.length)?{
    ????let?reorderPatch?=?{?type:?REORDER,?moves:?diffs.moves?}
    ????currentPatch.push(reorderPatch)
    ??}

    ??//?存放當前node的標識,初始化值為?0
    ??let?currentNodeIndex?=?index

    ??oldChildren.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 方法實現(xiàn)那一章節(jié)進行強懟,可能會比較帶勁吧!也希望小伙伴們可以和我達成共識(因為我自己原來好像也是這樣干的)。

    2、listDiff實現(xiàn) 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 操作的結(jié)合)。所以最后我們得出上圖中的一個 moves 數(shù)組,存儲了所有節(jié)點移動類的操作。

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

    /**
    ?*?Diff?two?list?in?O(N).
    ?*?@param?{Array}?oldList?-?原始列表
    ?*?@param?{Array}?newList?-?經(jīng)過一些操作的得出的新列表
    ?*?@return?{Object}?-?{moves:?}
    ?*??????????????????-?moves?list操作記錄的集合
    ?*/
    function?diff?(oldList,?newList,?key)?{
    ??let?oldMap?=?getKeyIndexAndFree(oldList,?key)
    ??let?newMap?=?getKeyIndexAndFree(newList,?key)

    ??let?newFree?=?newMap.free

    ??let?oldKeyIndex?=?oldMap.keyIndex
    ??let?newKeyIndex?=?newMap.keyIndex
    ??//?記錄所有move操作
    ??let?moves?=?[]

    ??//?a?simulate?list
    ??let?children?=?[]
    ??let?i?=?0
    ??let?item
    ??let?itemKey
    ??let?freeIndex?=?0

    ??//?newList?向?oldList?的形式靠近進行操作
    ??while?(i?????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?=?0
    ??while?(i?????if?(simulateList[i]?===?null)?{
    ??????remove(i)
    ??????removeSimulate(i)
    ????}?else?{
    ??????i++
    ????}
    ??}
    ??//?i??=>?new?list
    ??//?j??=>?simulateList
    ??let?j?=?i?=?0
    ??while?(i?????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í)行?insert
    ??????????insert(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++?//???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轉(zhuǎn)變成??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?????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

    四、實現(xiàn) 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
    ????:?0
    ??for?(let?i?=?0;?i?//?深度遍歷子節(jié)點
    ????let?child?=?node.childNodes[i]
    ????walker.index++
    ????walk(child,?walker,?patches)
    ??}

    ??if?(currentPatches)?{
    ????dealPatches(node,?currentPatches)??//?對當前節(jié)點進行DOM操作
    ??}
    }

    歷史總是驚人的相似,現(xiàn)在小伙伴應該知道之前深度遍歷給 Element 每個節(jié)點加上唯一標識的好處了吧。OK,接下來我們根據(jù)不同類型的差異對當前節(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)
    ????????break
    ??????case?REORDER:
    ????????reorderChildren(node,?currentPatch.moves)
    ????????break
    ??????case?ATTRS:
    ????????setProps(node,?currentPatch.props)
    ????????break
    ??????case?TEXT:
    ????????if?(node.textContent)?{
    ??????????node.textContent?=?currentPatch.content
    ????????}?else?{
    ??????????//?for?ie
    ??????????node.nodeValue?=?currentPatch.content
    ????????}
    ????????break
    ??????default:
    ????????throw?new?Error('Unknown?patch?type?'?+?currentPatch.type)
    ????}
    ??})
    }

    具體的 setAttrs 和 reorder 的實現(xiàn)如下

    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.index
    ????if?(move.type?===?0)?{?//?remove?item
    ??????if?(staticNodeList[index]?===?node.childNodes[index])?{?//?maybe?have?been?removed?for?inserting
    ????????node.removeChild(node.childNodes[index])
    ??????}
    ??????staticNodeList.splice(index,?1)
    ????}?else?if?(move.type?===?1)?{?//?insert?item
    ??????let?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 方法也得以實現(xiàn)了,virtual dom && diff 也算完成了,終于可以松一口氣了。能夠看到這里的小伙伴們,給你們一個大大的贊。

    總結(jié)

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

    - End -?

    有興趣同學可以關注微信公眾號奶爸碼農(nóng),持續(xù)分享關于投資理財、大前端技術(shù)、個人成長的內(nèi)容:

    總結(jié)

    以上是生活随笔為你收集整理的textarea实现datalist效果_手把手撸代码实现Virtual Dom amp;amp; Diff的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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