1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Vue 3.0 diff 算法及原理

        共 646字,需瀏覽 2分鐘

         ·

        2021-01-23 00:24

        (給前端大全加星標(biāo),提升前端技能

        轉(zhuǎn)自:GitHub - liuhanqu

        Vue 3.0 采取的 diff 算法和 2.0 的雙端比較有點不同。大概的原理如下

        //?c1:?a?b?[?c?d?e?]?f?g??
        //?c2:?a?b?[?d?e?c?h?]?f?g

        假如有如上的 c1 和 c2 新舊 children,在 diff 的時候,會有一個預(yù)處理的過程。

        先從前往后比較,當(dāng)節(jié)點不同時,不再往后進(jìn)行比較。接著又從后往前進(jìn)行比較,當(dāng)節(jié)點不同時,不再往前進(jìn)行比較。

        經(jīng)過預(yù)處理之后,c1 和 c2 真正需要進(jìn)行 diff 的部分如下所示:

        //?c1:?c?d?e
        //?c2:?d?e?c?h

        最后利用 “最長遞增子序列”,完成上述差異部分的比較,提高 diff 效率。

        處理相同的前后節(jié)點

        預(yù)處理過程代碼如下所示:

        const?patchKeyedChildren?=?(
        ????c1,
        ????c2,
        ????container,
        ????parentAnchor,
        ????parentComponent,
        ????parentSuspense,
        ????isSVG,
        ????optimized
        )?=>?{
        ????let?i?=?0;??
        ????const?l2?=?c2.length
        ????let?e1?=?c1.length?-?1
        ????let?e2?=?c2.length?-?1

        ????//?1.?sync?from?start
        ????while?(i?<=?e1?&&?i?<=?e2)?{
        ??????const?n1?=?c1[i]
        ??????const?n2?=?c2[i]
        ??????if?(isSameVNodeType(n1,?n2))?{
        ????????patch(
        ??????????n1,
        ??????????n2,
        ??????????container,
        ??????????parentAnchor,
        ??????????parentComponent,
        ??????????parentSuspense,
        ??????????isSVG,
        ??????????optimized
        ????????)
        ??????}?else?{
        ????????break
        ??????}
        ??????i++
        ????}

        ????//?2.?sync?from?end
        ????while?(i?<=?e1?&&?i?<=?e2)?{
        ??????const?n1?=?c1[e1]
        ??????const?n2?=?c2[e2]
        ??????if?(isSameVNodeType(n1,?n2))?{
        ????????patch(
        ??????????n1,
        ??????????n2,
        ??????????container,
        ??????????parentAnchor,
        ??????????parentComponent,
        ??????????parentSuspense,
        ??????????isSVG,
        ??????????optimized
        ????????)
        ??????}?else?{
        ????????break
        ??????}
        ??????e1--
        ??????e2--
        ????}
        }

        僅有節(jié)點新增或移除

        進(jìn)行預(yù)處理還有一個好處,就是在某些情況下,我們可以明確的知道有節(jié)點的新增或者刪除。

        • 節(jié)點新增

        i、e1、e2 滿足下述關(guān)系時,可以認(rèn)為是有節(jié)點新增

        //?3.?common?sequence?+?mount
        //?(a?b)
        //?(a?b)?c
        //?i?=?2,?e1?=?1,?e2?=?2
        //?(a?b)
        //?c?(a?b)
        //?i?=?0,?e1?=?-1,?e2?=?0
        if?(i?>?e1)?{
        ????if?(i?<=?e2)?{
        ????????const?nextPos?=?e2?+?1;
        ????????const?anchor?=?nextPos?
        ????????while?(i?<=?e2)?{
        ????????????patch(
        ????????????????null,
        ????????????????c2[i],
        ????????????????container,
        ????????????????anchor,
        ????????????????parentComponent,
        ????????????????parentSuspense,
        ????????????????isSVG
        ????????????)
        ????????????i++
        ????????}
        ????}
        }?else?if?{
        ????//
        }?else?{
        ????//
        }

        • 節(jié)點移除

        i、e1、e2 滿足下述關(guān)系時,可以認(rèn)為是有節(jié)點被移除

        //?4.?common?sequence?+?unmount
        //?(a?b)?c
        //?(a?b)
        //?i?=?2,?e1?=?2,?e2?=?1
        //?a?(b?c)
        //?(b?c)
        //?i?=?0,?e1?=?0,?e2?=?-1
        if?(i?>?e1)?{
        ??//
        }?else?if?(i?>?e2)?{
        ????while?(i?<=?e1)?{
        ????????unmount(c1[i],?parentComponent,?parentSuspense,?true)
        ????????i++
        ????}
        }?else?{
        ????//
        }

        有節(jié)點移動、新增或刪除

        有時候情況可能沒有上述那么地簡單,即 i、e1、e2 并不滿足上述兩種情形時,我們就要尋找其中需要被移除、新增的節(jié)點,又或是判斷哪些節(jié)點需要進(jìn)行移動。

        為此,我們需要去遍歷 c1 中還沒有進(jìn)行處理的節(jié)點,然后查看在 c2 中是否有對應(yīng)的節(jié)點(key 相同)。沒有,則說明該節(jié)點已經(jīng)被移除,那就執(zhí)行 unmount 操作。

        首先,為了快速確認(rèn) c1 的節(jié)點在 c2 中是否有對應(yīng)的節(jié)點及所在的位置,對 c2 中的節(jié)點建立一個映射 (key: index)

        //?5.?unknown?sequence
        //?[i?...?e1?+?1]:?a?b?[c?d?e]?f?g
        //?[i?...?e2?+?1]:?a?b?[d?e?c?h]?f?g
        //?i?=?2,?e1?=?4,?e2?=?5
        if?(i?>?e1)?{
        ??//
        }?else?if?(i?>?e2)?{
        ??//
        }?else?{
        ????const?s1?=?i
        ????const?s2?=?i

        ????const?keyToNewIndexMap?=?new?Map()

        ????//?5.1?build?key:index?map?for?newChildren
        ????for?(i?=?s2;?i?<=?e2;?i++)?{
        ????????const?nextChild?=?c2[i]
        ????????if?(nextChild.key?!==?null)?{
        ????????????keyToNewIndexMap.set(nextChild.key,?i)
        ????????}
        ????}
        }

        接著,定義以下幾個變量

        let?j?
        let?patched?=?0
        const?toBePatched?=?e2?-?s2?+?1??//?c2?中待處理的節(jié)點數(shù)目
        let?moved?=?false

        //?used?to?track?whether?any?node?has?moved
        let?maxNewIndexSoFar?=?0??//?已遍歷的待處理的?c1?節(jié)點在?c2?中對應(yīng)的索引最大值

        //?works?as?Map
        //?Note?that?oldIndex?is?offset?by?+1
        //?and?oldIndex?=?0?is?a?special?value?indicating?the?new?node?has
        //?no?corresponding?old?node.
        //?used?for?determining?longest?stable?subsequence
        const?newIndexToOldIndexMap?=?new?Array(toBePatched)?//?用于后面求最長遞增子序列

        for?(i?=?0;?i?????newIndexToOldIndexMap[i]?=?0
        }

        然后,遍歷 c1 中待處理的節(jié)點,判斷否 c2 中是有相同 key 的節(jié)點存在。

        • 沒有,說明該節(jié)點已經(jīng)被移除,unmount。
        • 有,調(diào)用 patch 函數(shù)。并記錄節(jié)點在 c1 中的索引。同時,記錄節(jié)點在 c2 中的最大索引,假如節(jié)點在 c2 中的索引位置小于這個最大索引,那么說明是有元素需要進(jìn)行移動。
        //?5.2?loop?through?old?children?left?to?be?patched?and?try?to?patch
        //?matching?nodes?&?remove?nodes?that?are?no?longer?present
        for?(i?=?s1;?i?<=?e1;?i++)?{
        ????const?prevChild?=?c1[i]

        ????//?(A)

        ????let?newIndex?
        ????if?(prevChild.key?!==?null)?{
        ????????newIndex?=?keyToNewIndexMap.get(prevChild.key)
        ????}?else?{
        ????????for?(j?=?s2;?i?<=?e2;?j++)?{
        ????????????if?(
        ??????????????newIndexToOldIndexMap[j?-?s2]?===?0?&&
        ??????????????isSameVNodeType(prevChild,?c2[j])
        ????????????)?{
        ??????????????newIndex?=?j
        ??????????????break
        ????????????}
        ????????}
        ????}

        ????if?(newIndex?===?void?0)?{
        ????????unmount(prevChild,?parentComponent,?parentSuspense,?true)
        ????}?else?{
        ????????newIndexToOldIndexMap[newIndex??-?s2]?=?i?+?1??//?(B)

        ????????if?(newIndex?>=?maxNewIndexSoFar)?{
        ????????????maxNewIndexSoFar?=?newIndex
        ????????}?else?{
        ????????????moved?=?true
        ????????}
        ????????patch(
        ????????????prevChild,
        ????????????c2[i],
        ????????????container,
        ????????????null,
        ????????????parentComponent,
        ????????????parentSuspense,
        ????????????isSVG,
        ????????????optimized
        ????????)

        ????????patched++??//?(C)
        ????}
        }

        是不是 c1 中的所有節(jié)點都需要在 c2 中尋找對應(yīng)節(jié)點,然后調(diào)用 patch 呢。

        注意到上面的代碼 (C),我們會更新已經(jīng) patched 的節(jié)點的數(shù)目,那么當(dāng) patched > toBePatched,可以認(rèn)為接下來遍歷的 c1 中的節(jié)點都是多余的了,直接移除就好。

        所以在上面的 (A) 處需要補充一下代碼

        if?(patched?>=?toBePatched)?{
        ????//?all?new?children?have?been?patched?so?this?can?only?be?a?removal
        ????unmount(prevChild,?parentComponent,?parentSuspense,?true)
        ????continue
        }

        到這里,就是較難理解的部分了。

        開篇我們說過,預(yù)處理過后,剩下的節(jié)點會借助最長遞增子序列來提高 diff 效率。

        求解最長遞增子序列,主要的目的就是為了減少 dom 元素的移動,也可以理解為最少的 dom 操作。

        首先,我們需要求解得到最長遞增子序列

        //?generate?longest?stable?subsequence?only?when?nodes?have?moved
        const?increasingNewIndexSequence?=?moved
        ??????getSequence(newIndexToOldIndexMap)
        ????:?EMPTY_ARR?

        先看看這里的 newIndexToOldIndexMap 的值是什么。

        結(jié)合一下具體的例子,假設(shè) c1 、c2 如下圖所示

        801a19b405f7b48c78242d5890b93b2b.webpimage

        定義并初始化 newIndexToOldIndexMap

        const?newIndexToOldIndexMap?=?new?Array(toBePatched)

        for?(i?=?0;?i?????newIndexToOldIndexMap[i]?=?0
        }

        toBePatched 即預(yù)處理后,c2 中待處理的節(jié)點數(shù)目。對應(yīng)這里的例子,會有

        toBePatched?=?4
        newIndexToOldIndexMap?=?[0,?0,?0,?0]

        注意到上面 5.2 遍歷 c1 中節(jié)點的代碼的 (B) 處,有

        //?這里是?i?+?1,不是?i?
        //?因為?0?是一個特殊值,表示這個是新增的節(jié)點
        newIndexToOldIndexMap[newIndex??-?s2]?=?i?+?1??//?(B)

        所以處理完 c1 中的節(jié)點后,將有

        moved?=?true
        newIndexToOldIndexMap?=?[4,?5,?3,?0]

        那么,increasingNewIndexSequence 的值就是 getSequence(newIndexToOldIndexMap) 的返回值

        //?[4,?5,?3,?0]??-->?最長遞增子序列是?[4,?5]?
        //?對應(yīng)的索引是?[0,?1]
        increasingNewIndexSequence?=?[0,?1]

        在求解得到最長遞增子序列之后,剩下的就是遍歷 c2 中的待處理節(jié)點,判斷是否節(jié)點是否屬于新增,是否需要進(jìn)行移動。

        j?=?increasingNewIndexSequence.length?-?1
        //?looping?backwards?so?that?we?can?use?last?patched?node?as?anchor
        //?注意:這里是從后往前遍歷
        for?(i?=?toBePatched?-?1;?i?>=?0;?i--)?{
        ????const?nextIndex?=?s2?+?i
        ????const?nextChild?=?c2[nextIndex]
        ????const?anchor?=
        ????????nextIndex?+?1?
        ????//?newIndexToOldIndexMap?里的值默認(rèn)初始化為?0?
        ????//?這里?===?0?表示?c2?中的節(jié)點在?c1?中沒有對應(yīng)的節(jié)點,屬于新增
        ????if?(newIndexToOldIndexMap[i]?===?0)?{
        ????????//?mount?new
        ????????patch(
        ????????????null,
        ????????????nextChild,
        ????????????container,
        ????????????anchor,
        ????????????parentComponent,
        ????????????parentSuspense,
        ????????????isSVG
        ????????)
        ????}?else?if?(moved)?{
        ????????//?move?if:
        ????????//?There?is?no?stable?subsequence?(e.g.?a?reverse)
        ????????//?OR?current?node?is?not?among?the?stable?sequence
        ????????
        ????????//?j??最長遞增子序列為?[]?
        ????????if?(j?????????????move(nextChild,?container,?anchor,?MoveType.REORDER)
        ????????}?else?{
        ????????????j--
        ????????}
        ????}
        }

        最長遞增子序列

        在計算機科學(xué)中,最長遞增子序列(longest increasing subsequence)問題是指,在一個給定的數(shù)值序列中,找到一個子序列,使得這個子序列元素的數(shù)值依次遞增,并且這個子序列的長度盡可能地大。最長遞增子序列中的元素在原序列中不一定是連續(xù)的。-- 維基百科

        對于以下的原始序列

        0,?8,?4,?12,?2,?10,?6,?14,?1,?9,?5,?13,?3,?11,?7,?15

        最長遞增子序列為

        0,?2,?6,?9,?11,?15.

        值得注意的是原始序列的最長遞增子序列并不一定唯一,對于該原始序列,實際上還有以下兩個最長遞增子序列

        0,?4,?6,?9,?11,?15
        0,?4,?6,?9,?13,?15

        最后

        至此,Vue 3.0 的 diff 代碼就分析完了,歡迎一起討論。

        具體的代碼:https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/renderer.ts



        - EOF -


        推薦閱讀??點擊標(biāo)題可跳轉(zhuǎn)

        1、Vue3 DOM Diff 核心算法解析

        2、詳解 React 16 的 Diff 策略

        3、Vitural Dom & diff算法 (一) 偽代碼實現(xiàn)


        覺得本文對你有幫助?請分享給更多人

        推薦關(guān)注「前端大全」,提升前端技能

        點贊和在看就是最大的支持??

        瀏覽 36
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            肏屄视频 | 一级特黄60分钟免费看软件下载 | 午夜影院中文字幕 | 伊人大香蕉伊人 | 啊~做一次h~嗯~H男男 | 青草操逼| 国产色片在线 | 99国产精品久久 | 熟女嗷嗷叫高潮合集91 | 看免费操逼片 |