首頁 > web前端 > js教程 > 如何使用vue內diff演算法

如何使用vue內diff演算法

php中世界最好的语言
發布: 2018-05-26 10:44:01
原創
1799 人瀏覽過

這次帶給大家如何使用vue內diff演算法,使用vue內diff演算法的注意事項有哪些,以下就是實戰案例,一起來看一下。

虛擬dom

diff演算法首先要明確一個概念就是diff的物件是虛擬dom,更新真​​實dom則是diff演算法的結果

Vnode基底類別

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

 constructor (

  。。。

 ) {

  this.tag = tag

  this.data = data

  this.children = children

  this.text = text

  this.elm = elm

  this.ns = undefined

  this.context = context

  this.fnContext = undefined

  this.fnOptions = undefined

  this.fnScopeId = undefined

  this.key = data && data.key

  this.componentOptions = componentOptions

  this.componentInstance = undefined

  this.parent = undefined

  this.raw = false

  this.isStatic = false

  this.isRootInsert = true

  this.isComment = false

  this.isCloned = false

  this.isOnce = false

  this.asyncFactory = asyncFactory

  this.asyncMeta = undefined

  this.isAsyncPlaceholder = false

 }

登入後複製

這個部分的程式碼主要是為了更好地知道在diff演算法中具體diff的屬性的意義,當然也可以更好地了解vnode實例

整體過程

核心函數是patch函數

  • #isUndef判斷(是不是undefined或null)

  • #// empty mount (likely as component), create new root elementcreateElm(vnode, insertedVnodeQueue) 這裡可以發現創建節點不是一個一個插入,而是放入一個隊列中統一批次

  • #核心函數sameVnode

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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)

   )

  )

 )

}

登入後複製

這裡是一個外層的比較函數,直接去比較了兩個節點的key,tag(標籤),data的比較(注意這裡的data指的是VNodeData),input的話直接比較type。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

export interface VNodeData {

 key?: string | number;

 slot?: string;

 scopedSlots?: { [key: string]: ScopedSlot };

 ref?: string;

 tag?: string;

 staticClass?: string;

 class?: any;

 staticStyle?: { [key: string]: any };

 style?: object[] | object;

 props?: { [key: string]: any };

 attrs?: { [key: string]: any };

 domProps?: { [key: string]: any };

 hook?: { [key: string]: Function };

 on?: { [key: string]: Function | Function[] };

 nativeOn?: { [key: string]: Function | Function[] };

 transition?: object;

 show?: boolean;

 inlineTemplate?: {

  render: Function;

  staticRenderFns: Function[];

 };

 directives?: VNodeDirective[];

 keepAlive?: boolean;

}

登入後複製

這會確認兩個節點是否有進一步比較的價值,不然直接替換

替換的過程主要是一個createElm函數另外則是銷毀oldVNode

1

2

3

4

5

6

// destroy old node

    if (isDef(parentElm)) {

     removeVnodes(parentElm, [oldVnode], 0, 0)

    else if (isDef(oldVnode.tag)) {

     invokeDestroyHook(oldVnode)

    }

登入後複製

插入過程簡化來說就是判斷node的type分別呼叫

createComponent(會判斷是否有children然後遞迴呼叫)

createComment

createTextNode

#建立後使用insert函數

之後需要用hydrate函數將虛擬dom和真是dom進行映射

1

2

3

4

5

6

7

8

9

10

11

function insert (parent, elm, ref) {

  if (isDef(parent)) {

   if (isDef(ref)) {

    if (ref.parentNode === parent) {

     nodeOps.insertBefore(parent, elm, ref)

    }

   else {

    nodeOps.appendChild(parent, elm)

   }

  }

 }

登入後複製

核心函數

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

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

  if (oldVnode === vnode) {

   return

  }

  const elm = vnode.elm = oldVnode.elm

  if (isTrue(oldVnode.isAsyncPlaceholder)) {

   if (isDef(vnode.asyncFactory.resolved)) {

    hydrate(oldVnode.elm, vnode, insertedVnodeQueue)

   else {

    vnode.isAsyncPlaceholder = true

   }

   return

  }

  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

  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)) {

   for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)

   if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)

  }

  if (isUndef(vnode.text)) {

   if (isDef(oldCh) && isDef(ch)) {

    if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)

   } else if (isDef(ch)) {

    if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, &#39;&#39;)

    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)

   } else if (isDef(oldCh)) {

    removeVnodes(elm, oldCh, 0, oldCh.length - 1)

   } else if (isDef(oldVnode.text)) {

    nodeOps.setTextContent(elm, &#39;&#39;)

   }

  } else if (oldVnode.text !== vnode.text) {

   nodeOps.setTextContent(elm, vnode.text)

  }

  if (isDef(data)) {

   if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)

  }

 }

登入後複製
##const el = vnode.el = oldVnode.el 這是很重要的一步,讓vnode.el引用到現在的真實dom,當el修改時,vnode.el會同步變化。

  1. 比較二者引用是否一致

  2. #之後asyncFactory不知道是做什麼的,所以這個比較看不懂

  3. 靜態節點比較key,相同後也不做重新渲染,直接拷貝componentInstance(once指令在此生效)

  4. 如果vnode是文字節點或

    註解節點,但vnode.text != oldVnode.text時,只需要更新vnode.elm的文字內容就可以

  5. children的比較

  • 如果只有oldVnode有子節點,那就把這些節點都

    刪除

  • 如果只有vnode有子節點,那就創建這些子節點,這裡如果oldVnode是個文字節點就把vnode.elm的文字設定為空

    字串

  • 都有則updateChildren,這個之後詳述

  • 如果oldVnode和vnode都沒有子節點,但是oldVnode是文字節點或註解節點,就把vnode.elm的文字設定為空字串

updateChildren

這部分重點還是關注整個演算法

首先四個指針,oldStart,oldEnd,newStart,newEnd,兩個數組,oldVnode,Vnode。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

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

  let oldStartIdx = 0

  let newStartIdx = 0

  let oldEndIdx = oldCh.length - 1

  let oldStartVnode = oldCh[0]

  let oldEndVnode = oldCh[oldEndIdx]

  let newEndIdx = newCh.length - 1

  let newStartVnode = newCh[0]

  let newEndVnode = newCh[newEndIdx]

  let oldKeyToIdx, idxInOld, vnodeToMove, refElm

  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

   if (isUndef(oldStartVnode)) {

    oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left

   } else if (isUndef(oldEndVnode)) {

    oldEndVnode = oldCh[--oldEndIdx]

   } else if (sameVnode(oldStartVnode, newStartVnode)) {

    patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)

    oldStartVnode = oldCh[++oldStartIdx]

    newStartVnode = newCh[++newStartIdx]

   } else if (sameVnode(oldEndVnode, newEndVnode)) {

    patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)

    oldEndVnode = oldCh[--oldEndIdx]

    newEndVnode = newCh[--newEndIdx]

   } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right

    patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)

    canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))

    oldStartVnode = oldCh[++oldStartIdx]

    newEndVnode = newCh[--newEndIdx]

   } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left

    patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)

    canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)

    oldEndVnode = oldCh[--oldEndIdx]

    newStartVnode = newCh[++newStartIdx]

   } else {

    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 {

     vnodeToMove = oldCh[idxInOld]

     if (sameVnode(vnodeToMove, newStartVnode)) {

      patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)

      oldCh[idxInOld] = undefined

      canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)

     } else {

      // same key but different element. treat as new element

      createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)

     }

    }

    newStartVnode = newCh[++newStartIdx]

   }

  }

  if (oldStartIdx > oldEndIdx) {

   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm

   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)

  else if (newStartIdx > newEndIdx) {

   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)

  }

 }

登入後複製
一個

循環比較的幾個情況和處理(以下的--均指index的--)比較則是比較的node節點,簡略寫法不嚴謹比較用的是sameVnode函數也不是真的全等

整體循環不結束的條件oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx

  1. #oldStart === newStart,oldStart newStart

  2. oldEnd === newEnd,oldEnd-- newEnd--

  3. oldStart === newEnd, oldStart插在隊伍末端oldStart newEnd --

  4. oldEnd === newStart, oldEnd插到隊伍開頭oldEnd-- newStart

  5. 剩下的所有情况都走这个处理简单的说也就两种处理,处理后newStart++

  • newStart在old中发现一样的那么将这个移动到oldStart前

  • 没有发现一样的那么创建一个放到oldStart之前

循环结束后并没有完成

还有一段判断才算完

1

2

3

4

5

6

7

8

9

if (oldStartIdx > oldEndIdx) {

   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm

   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)

  else if (newStartIdx > newEndIdx) {

   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)

  }<p>相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!</p>

<p>推荐阅读:</p>

<p style="text-align: left;"><a href="https://www.php.cn/js-tutorial-398443.html" target="_blank">怎样使用angular4在多个组件中数据通信</a><br></p>

<p style="text-align: left;"><a href="https://www.php.cn/js-tutorial-398440.html" target="_blank">使用Vue单页应用时应该如何引用单独样式文件</a><br></p><p>以上是如何使用vue內diff演算法的詳細內容。更多資訊請關注PHP中文網其他相關文章!</p>

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板