【Vuejs】1137- 詳解 Vue Diff 算法,值得精讀
一、虛擬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)?
先根據(jù)真實(shí)DOM生成一顆虛擬DOM樹 當(dāng)某個(gè)DOM節(jié)點(diǎn)數(shù)據(jù)發(fā)生改變時(shí),生成一個(gè)新的Vnode 新的Vnode和舊的oldVnode進(jìn)行對比 通過patch函數(shù)一邊比對一邊給真實(shí)DOM打補(bǔ)丁或者創(chuàng)建Vnode、移除oldVnode等
有什么不一樣?
真實(shí)DOM操作為一個(gè)屬性一個(gè)屬性去修改,開銷較大。 虛擬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)

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

三、 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ù):
oldVnode:舊的虛擬節(jié)點(diǎn) Vnode:新的虛擬節(jié)點(diǎn) hydrating:是否要和真實(shí)DOM混合 removeOnly:特殊的flag,用于 transition-group
處理流程大致分為以下步驟:
vnode不存在,oldVnode存在時(shí),移除oldVnode vnode存在,oldVnode不存在時(shí),創(chuàng)建vnode vnode和oldVnode都存在時(shí) 如果vnode和oldVnode是同一個(gè)節(jié)點(diǎn)(通過sameVnode函數(shù)對比 ?后續(xù)詳解),通過patchVnode進(jìn)行后續(xù)比對工作 如果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)?流程如下:
判斷Key值是否一樣 tag的值是否一樣 isComment,這個(gè)不用太關(guān)注。 數(shù)據(jù)一樣 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í)行流程:
如果oldVnode和vnode引用一致,可以認(rèn)為沒有變化,return 如果oldVnode的isAsyncPlaceholder屬性為true,跳過檢查異步組件,return 如果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 如果vnode不是文本節(jié)或注釋節(jié)點(diǎn) 如果vnode和oldVnode都有子節(jié)點(diǎn)并且兩者子節(jié)點(diǎn)不一致時(shí),就調(diào)用updateChildren更新子節(jié)點(diǎn) 如果只有vnode有自子節(jié)點(diǎn),則調(diào)用addVnodes創(chuàng)建子節(jié)點(diǎn) 如果只有oldVnode有子節(jié)點(diǎn),則調(diào)用removeVnodes把這些子節(jié)點(diǎn)都刪除 如果vnode文本為undefined,則清空vnode.elm文本
如果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í)行思路如下:
vnode頭對比oldVnode頭
vnode尾對比oldVnode尾
vnode頭對比oldVnode尾
vnode尾對比oldVnode頭
只要符合一種情況就進(jìn)行patch,移動(dòng)節(jié)點(diǎn),移動(dòng)下標(biāo)等操作
都不對再在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é)
正確使用key,可以快速執(zhí)行sameVnode比對,加速Diff效率,可以作為性能優(yōu)化的一個(gè)點(diǎn)。 DIff只做同級比較,使用sameVnode函數(shù)比對,文本節(jié)點(diǎn)直接替換文本內(nèi)容。 子元素列表的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評論圖片表情
在线肏屄视频
|
九九九在线视频
|
奇米久久色
|
操B免费观看
|
亚洲黄色网上视频
|
