<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【Vuejs】1137- 詳解 Vue Diff 算法,值得精讀

          共 3839字,需瀏覽 8分鐘

           ·

          2021-11-10 09:32

          一、虛擬DOM

          什么是虛擬DOM?

          虛擬DOM就是把真實(shí)DOM樹的結(jié)構(gòu)和信息抽象出來,以對象的形式模擬樹形結(jié)構(gòu),如下:

          真實(shí)DOM:

          <div>
          ????<p>Hello?Worldp>
          div>

          對應(yīng)的虛擬DOM就是:

          let?vnode?=?{
          ????tag:?'div',
          ????children:[?{tag:'p',?text:'Hello?World'}]
          }

          為什么需要虛擬DOM?

          渲染真實(shí)DOM會(huì)有一定的開銷,如果每次修改數(shù)據(jù)都進(jìn)行真實(shí)DOM渲染,都會(huì)引起DOM樹的重繪和重排,性能開銷很大。那么有沒有可能只修改一小部分?jǐn)?shù)據(jù)而不渲染整個(gè)DOM呢?虛擬DOM和Diff算法可以實(shí)現(xiàn)。

          怎么實(shí)現(xiàn)?

          1. 先根據(jù)真實(shí)DOM生成一顆虛擬DOM樹
          2. 當(dāng)某個(gè)DOM節(jié)點(diǎn)數(shù)據(jù)發(fā)生改變時(shí),生成一個(gè)新的Vnode
          3. 新的Vnode和舊的oldVnode進(jìn)行對比
          4. 通過patch函數(shù)一邊比對一邊給真實(shí)DOM打補(bǔ)丁或者創(chuàng)建Vnode、移除oldVnode等

          有什么不一樣?

          1. 真實(shí)DOM操作為一個(gè)屬性一個(gè)屬性去修改,開銷較大。
          2. 虛擬DOM直接修改整個(gè)DOM節(jié)點(diǎn)再替換真實(shí)DOM

          還有什么好處?

          Vue的虛擬DOM數(shù)據(jù)更新機(jī)制是異步更新隊(duì)列,并不是數(shù)據(jù)變更馬上更新DOM,而是被推進(jìn)一個(gè)數(shù)據(jù)更新異步隊(duì)列統(tǒng)一更新。想要馬上拿到DOM更新后DOM信息?有個(gè)API叫 Vue.nextTick

          二、 Diff算法

          傳統(tǒng)Diff算法

          遍歷兩棵樹中的每一個(gè)節(jié)點(diǎn),每兩個(gè)節(jié)點(diǎn)之間都要做一次比較。

          比如 a->e 、a->d 、a->b、a->c、a->a

          • 遍歷完成的時(shí)間復(fù)雜度達(dá)到了O(n^2)
          • 對比完差異后還要計(jì)算最小轉(zhuǎn)換方式,實(shí)現(xiàn)后復(fù)雜度來到了O(n^3)
          img

          Vue優(yōu)化的Diff算法

          Vue的diff算法只會(huì)比較同層級的元素,不進(jìn)行跨層級比較

          img

          三、 Vue中的Diff算法實(shí)現(xiàn)

          Vnode分類

          • EmptyVNode: 沒有內(nèi)容的注釋節(jié)點(diǎn)
          • TextVNode: 文本節(jié)點(diǎn)
          • ElementVNode: 普通元素節(jié)點(diǎn)
          • ComponentVNode: 組件節(jié)點(diǎn)
          • CloneVNode: 克隆節(jié)點(diǎn),可以是以上任意類型的節(jié)點(diǎn),唯一的區(qū)別在于isCloned屬性為true

          Patch函數(shù)

          patch函數(shù)接收以下參數(shù):

          1. oldVnode:舊的虛擬節(jié)點(diǎn)
          2. Vnode:新的虛擬節(jié)點(diǎn)
          3. hydrating:是否要和真實(shí)DOM混合
          4. removeOnly:特殊的flag,用于 transition-group

          處理流程大致分為以下步驟:

          1. vnode不存在,oldVnode存在時(shí),移除oldVnode
          2. vnode存在,oldVnode不存在時(shí),創(chuàng)建vnode
          3. vnode和oldVnode都存在時(shí)
            1. 如果vnode和oldVnode是同一個(gè)節(jié)點(diǎn)(通過sameVnode函數(shù)對比 ?后續(xù)詳解),通過patchVnode進(jìn)行后續(xù)比對工作
            2. 如果vnode和oldVnode不是同一個(gè)節(jié)點(diǎn),那么根據(jù)vnode創(chuàng)建新的元素并掛載至oldVnode父元素下。如果組件根節(jié)點(diǎn)被替換,遍歷更新父節(jié)點(diǎn)element。然后移除舊節(jié)點(diǎn)。如果oldVnode是服務(wù)端渲染元素節(jié)點(diǎn),需要用hydrate函數(shù)將虛擬dom和真是dom進(jìn)行映射

          源碼如下,已寫好注釋便于閱讀

          return?function?patch(oldVnode,?vnode,?hydrating,?removeOnly)?{
          ????//?如果vnode不存在,但是oldVnode存在,移除oldVnode
          ????if?(isUndef(vnode))?{
          ??????if?(isDef(oldVnode))?invokeDestroyHook(oldVnode)
          ??????return
          ????}

          ????let?isInitialPatch?=?false
          ????const?insertedVnodeQueue?=?[]

          ????//?如果oldVnode不存在,但是vnode存在時(shí),創(chuàng)建vnode
          ????if?(isUndef(oldVnode))?{
          ??????isInitialPatch?=?true
          ??????createElm(vnode,?insertedVnodeQueue)
          ????}?else?{
          ??????//?剩余情況為vnode和oldVnode都存在

          ??????//?判斷是否為真實(shí)DOM元素
          ??????const?isRealElement?=?isDef(oldVnode.nodeType)
          ??????if?(!isRealElement?&&?sameVnode(oldVnode,?vnode))?{
          ????????//?如果vnode和oldVnode是同一個(gè)(通過sameVnode函數(shù)進(jìn)行比對??后續(xù)詳解)
          ????????//?受用patchVnode函數(shù)進(jìn)行后續(xù)比對工作?(函數(shù)后續(xù)詳解)
          ????????patchVnode(oldVnode,?vnode,?insertedVnodeQueue,?removeOnly)
          ??????}?else?{
          ????????//?vnode和oldVnode不是同一個(gè)的情況
          ????????if?(isRealElement)?{
          ??????????//?如果存在真實(shí)的節(jié)點(diǎn),存在data-server-render屬性
          ??????????if?(oldVnode.nodeType?===?1?&&?oldVnode.hasAttribute(SSR_ATTR))?{
          ????????????//?當(dāng)舊的Vnode是服務(wù)端渲染元素,hydrating記為true
          ????????????oldVnode.removeAttribute(SSR_ATTR)
          ????????????hydrating?=?true
          ??????????}
          ??????????//?需要用hydrate函數(shù)將虛擬DOM和真實(shí)DOM進(jìn)行映射
          ??????????if?(isTrue(hydrating))?{
          ????????????//?需要合并到真實(shí)DOM上
          ????????????if?(hydrate(oldVnode,?vnode,?insertedVnodeQueue))?{
          ??????????????//?調(diào)用insert鉤子
          ??????????????invokeInsertHook(vnode,?insertedVnodeQueue,?true)
          ??????????????return?oldVnode
          ????????????}?else?if?(process.env.NODE_ENV?!==?'production')?{
          ??????????????warn(
          ????????????????'The?client-side?rendered?virtual?DOM?tree?is?not?matching?'?+
          ????????????????'server-rendered?content.?This?is?likely?caused?by?incorrect?'?+
          ????????????????'HTML?markup,?for?example?nesting?block-level?elements?inside?'?+
          ????????????????'

          ,?or?missing?.?Bailing?hydration?and?performing?'?+
          ????????????????'full?client-side?render.'
          ??????????????)
          ????????????}
          ??????????}
          ??????????//?如果不是服務(wù)端渲染元素或者合并到真實(shí)DOM失敗,則創(chuàng)建一個(gè)空的Vnode節(jié)點(diǎn)去替換它
          ??????????oldVnode?=?emptyNodeAt(oldVnode)
          ????????}

          ????????//?獲取oldVnode父節(jié)點(diǎn)
          ????????const?oldElm?=?oldVnode.elm
          ????????const?parentElm?=?nodeOps.parentNode(oldElm)

          ????????//?根據(jù)vnode創(chuàng)建一個(gè)真實(shí)DOM節(jié)點(diǎn)并掛載至oldVnode的父節(jié)點(diǎn)下
          ????????createElm(
          ??????????vnode,
          ??????????insertedVnodeQueue,
          ??????????oldElm._leaveCb???null?:?parentElm,
          ??????????nodeOps.nextSibling(oldElm)
          ????????)

          ????????//?如果組件根節(jié)點(diǎn)被替換,遍歷更新父節(jié)點(diǎn)Element
          ????????if?(isDef(vnode.parent))?{
          ??????????let?ancestor?=?vnode.parent
          ??????????const?patchable?=?isPatchable(vnode)
          ??????????while?(ancestor)?{
          ????????????for?(let?i?=?0;?i???????????????cbs.destroy[i](ancestor)
          ????????????}
          ????????????ancestor.elm?=?vnode.elm
          ????????????if?(patchable)?{
          ??????????????for?(let?i?=?0;?i?????????????????cbs.create[i](emptyNode,?ancestor)
          ??????????????}
          ??????????????//?#6513
          ??????????????//?invoke?insert?hooks?that?may?have?been?merged?by?create?hooks.
          ??????????????//?e.g.?for?directives?that?uses?the?"inserted"?hook.
          ??????????????const?insert?=?ancestor.data.hook.insert
          ??????????????if?(insert.merged)?{
          ????????????????//?start?at?index?1?to?avoid?re-invoking?component?mounted?hook
          ????????????????for?(let?i?=?1;?i???????????????????insert.fns[i]()
          ????????????????}
          ??????????????}
          ????????????}?else?{
          ??????????????registerRef(ancestor)
          ????????????}
          ????????????ancestor?=?ancestor.parent
          ??????????}
          ????????}

          ????????//?銷毀舊節(jié)點(diǎn)
          ????????if?(isDef(parentElm))?{
          ??????????//?移除老節(jié)點(diǎn)
          ??????????removeVnodes(parentElm,?[oldVnode],?0,?0)
          ????????}?else?if?(isDef(oldVnode.tag))?{
          ??????????//?調(diào)用destroy鉤子
          ??????????invokeDestroyHook(oldVnode)
          ????????}
          ??????}
          ????}
          ????//?調(diào)用insert鉤子并返回節(jié)點(diǎn)
          ????invokeInsertHook(vnode,?insertedVnodeQueue,?isInitialPatch)
          ????return?vnode.elm
          ??}

          sameVnode函數(shù)

          Vue怎么判斷是不是同一個(gè)節(jié)點(diǎn)?流程如下:

          1. 判斷Key值是否一樣
          2. tag的值是否一樣
          3. isComment,這個(gè)不用太關(guān)注。
          4. 數(shù)據(jù)一樣
          5. sameInputType(),專門對表單輸入項(xiàng)進(jìn)行判斷的:input一樣但是里面的type不一樣算不同的inputType

          從這里可以看出key對diff算法的輔助作用,可以快速定位是否為同一個(gè)元素,必須保證唯一性。

          如果你用的是index作為key,每次打亂順序key都會(huì)改變,導(dǎo)致這種判斷失效,降低了Diff的效率。

          因此,用好key也是Vue性能優(yōu)化的一種方式。

          • 源碼如下:
          function?sameVnode(a,?b)?{
          ??return?(
          ????a.key?===?b.key?&&?(
          ??????(
          ????????a.tag?===?b.tag?&&
          ????????a.isComment?===?b.isComment?&&
          ????????isDef(a.data)?===?isDef(b.data)?&&
          ????????sameInputType(a,?b)
          ??????)?||?(
          ????????isTrue(a.isAsyncPlaceholder)?&&
          ????????a.asyncFactory?===?b.asyncFactory?&&
          ????????isUndef(b.asyncFactory.error)
          ??????)
          ????)
          ??)
          }

          patchVnode函數(shù)

          前置條件vnode和oldVnode是同一個(gè)節(jié)點(diǎn)

          執(zhí)行流程:

          1. 如果oldVnode和vnode引用一致,可以認(rèn)為沒有變化,return
          2. 如果oldVnode的isAsyncPlaceholder屬性為true,跳過檢查異步組件,return
          3. 如果oldVnode跟vnode都是靜態(tài)節(jié)點(diǎn),且具有相同的key,同時(shí)vnode是克隆節(jié)點(diǎn)或者v-once指令控制的節(jié)點(diǎn)時(shí),只需要把oldVnode.elm和oldVnode.child都復(fù)制到vnode上,也不用再有其他操作,return
          4. 如果vnode不是文本節(jié)或注釋節(jié)點(diǎn)
            1. 如果vnode和oldVnode都有子節(jié)點(diǎn)并且兩者子節(jié)點(diǎn)不一致時(shí),就調(diào)用updateChildren更新子節(jié)點(diǎn)
            2. 如果只有vnode有自子節(jié)點(diǎn),則調(diào)用addVnodes創(chuàng)建子節(jié)點(diǎn)
            3. 如果只有oldVnode有子節(jié)點(diǎn),則調(diào)用removeVnodes把這些子節(jié)點(diǎn)都刪除
            4. 如果vnode文本為undefined,則清空vnode.elm文本
          5. 如果vnode是文本節(jié)點(diǎn)但是和oldVnode文本內(nèi)容不同,只需更新文本。

          源代碼如下,已寫好注釋便于閱讀

          function?patchVnode(oldVnode,?vnode,?insertedVnodeQueue,?removeOnly)?{

          ????//?如果新老節(jié)點(diǎn)引用一致,直接返回。
          ????if?(oldVnode?===?vnode)?{
          ??????return
          ????}

          ????const?elm?=?vnode.elm?=?oldVnode.elm

          ????//?如果oldVnode的isAsyncPlaceholder屬性為true,跳過檢查異步組件
          ????if?(isTrue(oldVnode.isAsyncPlaceholder))?{
          ??????if?(isDef(vnode.asyncFactory.resolved))?{
          ????????hydrate(oldVnode.elm,?vnode,?insertedVnodeQueue)
          ??????}?else?{
          ????????vnode.isAsyncPlaceholder?=?true
          ??????}
          ??????return
          ????}

          ????//?如果新舊都是靜態(tài)節(jié)點(diǎn),vnode的key也相同
          ????//?新vnode是克隆所得或新vnode有?v-once屬性
          ????//?則進(jìn)行賦值,然后返回。vnode的componentInstance 保持不變
          ????if?(isTrue(vnode.isStatic)?&&
          ??????isTrue(oldVnode.isStatic)?&&
          ??????vnode.key?===?oldVnode.key?&&
          ??????(isTrue(vnode.isCloned)?||?isTrue(vnode.isOnce))
          ????)?{
          ??????vnode.componentInstance?=?oldVnode.componentInstance
          ??????return
          ????}

          ????let?i
          ????const?data?=?vnode.data
          ????//?執(zhí)行data.hook.prepatch?鉤子
          ????if?(isDef(data)?&&?isDef(i?=?data.hook)?&&?isDef(i?=?i.prepatch))?{
          ??????i(oldVnode,?vnode)
          ????}

          ????//?獲取子元素列表
          ????const?oldCh?=?oldVnode.children
          ????const?ch?=?vnode.children

          ????if?(isDef(data)?&&?isPatchable(vnode))?{
          ??????//?遍歷調(diào)用?cbs.update?鉤子函數(shù),更新oldVnode所有屬性
          ??????//?包括attrs、class、domProps、events、style、ref、directives
          ??????for?(i?=?0;?i???????//?執(zhí)行data.hook.update?鉤子
          ??????if?(isDef(i?=?data.hook)?&&?isDef(i?=?i.update))?i(oldVnode,?vnode)
          ????}
          ????//?Vnode?的?text選項(xiàng)為undefined
          ????if?(isUndef(vnode.text))?{
          ??????if?(isDef(oldCh)?&&?isDef(ch))?{
          ????????//新老節(jié)點(diǎn)的children不同,執(zhí)行updateChildren方法
          ????????if?(oldCh?!==?ch)?updateChildren(elm,?oldCh,?ch,?insertedVnodeQueue,?removeOnly)
          ??????}?else?if?(isDef(ch))?{
          ????????//?oldVnode?children不存在?執(zhí)行?addVnodes方法
          ????????if?(isDef(oldVnode.text))?nodeOps.setTextContent(elm,?'')
          ????????addVnodes(elm,?null,?ch,?0,?ch.length?-?1,?insertedVnodeQueue)
          ??????}?else?if?(isDef(oldCh))?{
          ????????//?vnode不存在執(zhí)行removeVnodes方法
          ????????removeVnodes(elm,?oldCh,?0,?oldCh.length?-?1)
          ??????}?else?if?(isDef(oldVnode.text))?{
          ????????//?新舊節(jié)點(diǎn)都是undefined,且老節(jié)點(diǎn)存在text,清空文本。
          ????????nodeOps.setTextContent(elm,?'')
          ??????}
          ????}?else?if?(oldVnode.text?!==?vnode.text)?{
          ??????//?新老節(jié)點(diǎn)文本內(nèi)容不同,更新文本
          ??????nodeOps.setTextContent(elm,?vnode.text)
          ????}
          ????if?(isDef(data))?{
          ??????//?執(zhí)行data.hook.postpatch鉤子,至此?patch完成
          ??????if?(isDef(i?=?data.hook)?&&?isDef(i?=?i.postpatch))?i(oldVnode,?vnode)
          ????}
          ??}

          updateChildren函數(shù)

          重點(diǎn)!!!

          前置條件:vnode和oldVnode的children不相等

          整體的執(zhí)行思路如下:

          1. vnode頭對比oldVnode頭

          2. vnode尾對比oldVnode尾

          3. vnode頭對比oldVnode尾

          4. vnode尾對比oldVnode頭

            • 只要符合一種情況就進(jìn)行patch,移動(dòng)節(jié)點(diǎn),移動(dòng)下標(biāo)等操作
          5. 都不對再在oldChild中找一個(gè)key和newStart相同的節(jié)點(diǎn)

            • 如果是相同節(jié)點(diǎn),進(jìn)行patch ?然后將這個(gè)節(jié)點(diǎn)插入到oldStart之前,newStart下標(biāo)繼續(xù)移動(dòng)
            • 如果不是相同節(jié)點(diǎn),需要執(zhí)行createElm創(chuàng)建新元素
            • 找不到,新建一個(gè)。

            • 找到,獲取這個(gè)節(jié)點(diǎn),判斷它和newStartVnode是不是同一個(gè)節(jié)點(diǎn)

          為什么會(huì)有頭對尾、尾對頭的操作?

          • 可以快速檢測出reverse操作,加快diff效率。

          源碼如下 ? 已寫好注釋便于閱讀:

          ?function?updateChildren(parentElm,?oldCh,?newCh,?insertedVnodeQueue,?removeOnly)?{

          ????//?定義變量
          ????let?oldStartIdx?=?0??//?老節(jié)點(diǎn)Child頭下標(biāo)
          ????let?newStartIdx?=?0??//?新節(jié)點(diǎn)Child頭下標(biāo)
          ????let?oldEndIdx?=?oldCh.length?-?1??//?老節(jié)點(diǎn)Child尾下標(biāo)
          ????let?oldStartVnode?=?oldCh[0]??????//?老節(jié)點(diǎn)Child頭結(jié)點(diǎn)
          ????let?oldEndVnode?=?oldCh[oldEndIdx]?//?老節(jié)點(diǎn)Child尾結(jié)點(diǎn)
          ????let?newEndIdx?=?newCh.length?-?1???//?新節(jié)點(diǎn)Child尾下標(biāo)
          ????let?newStartVnode?=?newCh[0]???????//?新節(jié)點(diǎn)Child頭結(jié)點(diǎn)
          ????let?newEndVnode?=?newCh[newEndIdx]??//?新節(jié)點(diǎn)Child尾結(jié)點(diǎn)
          ????let?oldKeyToIdx,?idxInOld,?vnodeToMove,?refElm??

          ????//?removeOnly?is?a?special?flag?used?only?by?
          ????//?to?ensure?removed?elements?stay?in?correct?relative?positions
          ????//?during?leaving?transitions
          ????const?canMove?=?!removeOnly

          ????if?(process.env.NODE_ENV?!==?'production')?{
          ??????checkDuplicateKeys(newCh)
          ????}

          ????//?定義循環(huán)
          ????while?(oldStartIdx?<=?oldEndIdx?&&?newStartIdx?<=?newEndIdx)?{
          ??????//?存在檢測
          ??????if?(isUndef(oldStartVnode))?{
          ????????oldStartVnode?=?oldCh[++oldStartIdx]?//?Vnode?has?been?moved?left
          ??????}?else?if?(isUndef(oldEndVnode))?{
          ????????oldEndVnode?=?oldCh[--oldEndIdx]

          ??????//?如果老結(jié)點(diǎn)Child頭和新節(jié)點(diǎn)Child頭是同一個(gè)節(jié)點(diǎn)
          ??????}?else?if?(sameVnode(oldStartVnode,?newStartVnode))?{
          ????????//?patch差異
          ????????patchVnode(oldStartVnode,?newStartVnode,?insertedVnodeQueue)
          ????????//?patch完成??移動(dòng)節(jié)點(diǎn)位置??繼續(xù)比對下一個(gè)節(jié)點(diǎn)
          ????????oldStartVnode?=?oldCh[++oldStartIdx]
          ????????newStartVnode?=?newCh[++newStartIdx]

          ??????//?如果老結(jié)點(diǎn)Child尾和新節(jié)點(diǎn)Child尾是同一個(gè)節(jié)點(diǎn)
          ??????}?else?if?(sameVnode(oldEndVnode,?newEndVnode))?{
          ????????//?patch差異
          ????????patchVnode(oldEndVnode,?newEndVnode,?insertedVnodeQueue)
          ????????//?patch完成??移動(dòng)節(jié)點(diǎn)位置?繼續(xù)比對下一個(gè)節(jié)點(diǎn)
          ????????oldEndVnode?=?oldCh[--oldEndIdx]
          ????????newEndVnode?=?newCh[--newEndIdx]

          ??????//?如果老結(jié)點(diǎn)Child頭和新節(jié)點(diǎn)Child尾是同一個(gè)節(jié)點(diǎn)
          ??????}?else?if?(sameVnode(oldStartVnode,?newEndVnode))?{?//?Vnode?moved?right
          ?????????//?patch差異
          ????????patchVnode(oldStartVnode,?newEndVnode,?insertedVnodeQueue)
          ????????//?把oldStart節(jié)點(diǎn)放到oldEnd節(jié)點(diǎn)后面
          ????????canMove?&&?nodeOps.insertBefore(parentElm,?oldStartVnode.elm,?nodeOps.nextSibling(oldEndVnode.elm))
          ????????//?patch完成??移動(dòng)節(jié)點(diǎn)位置?繼續(xù)比對下一個(gè)節(jié)點(diǎn)
          ????????oldStartVnode?=?oldCh[++oldStartIdx]
          ????????newEndVnode?=?newCh[--newEndIdx]
          ??????//?如果老結(jié)點(diǎn)Child尾和新節(jié)點(diǎn)Child頭是同一個(gè)節(jié)點(diǎn)
          ??????}?else?if?(sameVnode(oldEndVnode,?newStartVnode))?{?//?Vnode?moved?left
          ?????????//?patch差異
          ????????patchVnode(oldEndVnode,?newStartVnode,?insertedVnodeQueue)
          ????????//?把oldEnd節(jié)點(diǎn)放到oldStart節(jié)點(diǎn)前面
          ????????canMove?&&?nodeOps.insertBefore(parentElm,?oldEndVnode.elm,?oldStartVnode.elm)
          ????????//?patch完成??移動(dòng)節(jié)點(diǎn)位置?繼續(xù)比對下一個(gè)節(jié)點(diǎn)
          ????????oldEndVnode?=?oldCh[--oldEndIdx]
          ????????newStartVnode?=?newCh[++newStartIdx]
          ??????}?else?{
          ????????//?如果沒有相同的Key,執(zhí)行createElm方法創(chuàng)建元素
          ????????if?(isUndef(oldKeyToIdx))?oldKeyToIdx?=?createKeyToOldIdx(oldCh,?oldStartIdx,?oldEndIdx)
          ????????idxInOld?=?isDef(newStartVnode.key)??
          ??????????oldKeyToIdx[newStartVnode.key]?:
          ??????????findIdxInOld(newStartVnode,?oldCh,?oldStartIdx,?oldEndIdx)
          ????????if?(isUndef(idxInOld))?{?//?New?element
          ??????????createElm(newStartVnode,?insertedVnodeQueue,?parentElm,?oldStartVnode.elm,?false,?newCh,?newStartIdx)
          ????????}?else?{
          ??????????//?有相同的Key,判斷這兩個(gè)節(jié)點(diǎn)是否為sameNode
          ??????????vnodeToMove?=?oldCh[idxInOld]
          ??????????if?(sameVnode(vnodeToMove,?newStartVnode))?{
          ????????????//?如果是相同節(jié)點(diǎn),進(jìn)行patch??然后舉將oldStart插入到oldStart之前,newStart下標(biāo)繼續(xù)移動(dòng)
          ????????????patchVnode(vnodeToMove,?newStartVnode,?insertedVnodeQueue)
          ????????????oldCh[idxInOld]?=?undefined
          ????????????canMove?&&?nodeOps.insertBefore(parentElm,?vnodeToMove.elm,?oldStartVnode.elm)
          ??????????}?else?{
          ????????????//?如果不是相同節(jié)點(diǎn),需要執(zhí)行createElm創(chuàng)建新元素
          ????????????createElm(newStartVnode,?insertedVnodeQueue,?parentElm,?oldStartVnode.elm,?false,?newCh,?newStartIdx)
          ??????????}
          ????????}
          ????????newStartVnode?=?newCh[++newStartIdx]
          ??????}
          ????}

          ????//?oldStartIdx?>?oldEndIdx說明oldChild先遍歷完,使用addVnode方法添加newStartIdx指向的節(jié)點(diǎn)到newEndIdx的節(jié)點(diǎn)
          ????if?(oldStartIdx?>?oldEndIdx)?{
          ??????refElm?=?isUndef(newCh[newEndIdx?+?1])???null?:?newCh[newEndIdx?+?1].elm
          ??????addVnodes(parentElm,?refElm,?newCh,?newStartIdx,?newEndIdx,?insertedVnodeQueue)
          ????}?else?if?(newStartIdx?>?newEndIdx)?{
          ??????//?如果newStartIdx?>?newEndIdx說明newChild先遍歷完,remove掉oldChild未遍歷完的節(jié)點(diǎn)
          ??????removeVnodes(parentElm,?oldCh,?oldStartIdx,?oldEndIdx)
          ????}
          ??}

          四、總結(jié)

          1. 正確使用key,可以快速執(zhí)行sameVnode比對,加速Diff效率,可以作為性能優(yōu)化的一個(gè)點(diǎn)。
          2. DIff只做同級比較,使用sameVnode函數(shù)比對,文本節(jié)點(diǎn)直接替換文本內(nèi)容。
          3. 子元素列表的Diff,進(jìn)行頭對頭、尾對尾、頭對尾等系列比較,直到遍歷完兩個(gè)元素的子元素列表。
            • 或一個(gè)列表先遍歷完了,直接addVnode / removeVnode。

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 120+ 篇原創(chuàng)文章

          瀏覽 35
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线肏屄视频 | 九九九在线视频 | 奇米久久色 | 操B免费观看 | 亚洲黄色网上视频 |