vue核心面试题:diff算法

小灰灰 2022-11-27 00:48 407阅读 0赞

一、diff算法的时间复杂度

两个树的完全的 diff 算法是一个时间复杂度为 O(n3) , Vue 进行了优化·O(n3) 复杂度的问题转换成 O(n) 复杂度的问题(只比较同级不考虑跨级问题),因为在前端操作dom的时候了,不会把当前元素作为上一级元素或下一级元素,很少会跨越层级地移动Dom元素,常见的都是同级的比较。 所 以 Virtual Dom只会对同一个层级的元素进行对比。

二、vue中diff算法的原理

在数据发生变化,vue是先根据真实DOM生成一颗 virtual DOM ,当 virtual DOM 某个节点的数据改变后会生成一个新的 Vnode ,然后 VnodeoldVnode 作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使 oldVnode 的值为 Vnode ,来实现更新节点。

1.原理简述:

(1)先去同级比较,然后再去比较子节点

(2)先去判断一方有子节点一方没有子节点的情况

(3)比较都有子节点的情况

(4)递归比较子节点

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDcyMDg2_size_16_color_FFFFFF_t_70

2.源码:

源码文件地址:src/core/vdom/patch.js

源码总结:

在进行节点比较时,是通过patch这个方法实现,其中如果不是真实元素并且用sameVnode看两个是否是同一个元素,如果是然后会调用patchVnode进行比较,比较的是虚拟节点不是真实节点,如果不值得去比较则用 Vnode 替换 oldVnode。patchVnode会比较这两个节点,判断 VnodeoldVnode 是否指向同一个对象,如果是,那么直接 return,复用老的真是元素。如果不是会用新的子节点和旧的字节做比较。如果他们都有文本节点并且不相等,那么将el的文本节点设置为Vnode的文本节点;如果两个都有子节点的情况下会调用updateChildren方法比较他们的子节点;只有新的节点有子节点而旧节点没有子节点,就会把新增的子节点插入到旧的dom中;如果当前新的节点中没有子节点,旧的中有子节点,然后就会把旧的节点删除掉;

updateChildren方法中,先定义了四对指针,先比较新的和旧的第一个子节点是否一样,如果一样会移动前面两个指针,以此类推,如果比对最后新的节点多了一个子节点就把它插入旧的中。如果开始第一个子节点不一样就从后面开始进行比较(从尾子节点开始比较),比到最后把新增的节点插入到旧的dom中。还有这种情况就是头和尾节点都不相等的情况下,用当前的头和旧的尾节点进行比较,如果一样就把旧的尾节点移到前面去,然后将旧的尾指针向前移动一位,当前头指针移动到下一个子节点上。另一种就是用旧的结尾和新的开头节点进行比较,四种比较策略。如果存在key,就会拿的当前子节点的key去旧的子节点中找,如果找到就将它移动到旧节点的前面,然后就将指针移向当前节点的第二个子节点上,以此类推,如果当前子节点没有就将它直接插入到旧的节点中,最后把旧的节点中多余出的子节点删除掉。

(1)patch

  1. return function patch (oldVnode, vnode, hydrating, removeOnly) {
  2. if (isUndef(vnode)) {
  3. if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
  4. return
  5. }
  6. let isInitialPatch = false
  7. const insertedVnodeQueue = []
  8. if (isUndef(oldVnode)) {
  9. // empty mount (likely as component), create new root element
  10. isInitialPatch = true
  11. createElm(vnode, insertedVnodeQueue)
  12. } else {
  13. const isRealElement = isDef(oldVnode.nodeType)
  14. // 如果不是真实元素并且两个是同一个元素的话,然后会调用patchVnode进行比较
  15. // 比较是虚拟节点不是真实节点
  16. if (!isRealElement && sameVnode(oldVnode, vnode)) {
  17. // patch existing root node
  18. patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
  19. } else {
  20. if (isRealElement) {
  21. // mounting to a real element
  22. // check if this is server-rendered content and if we can perform
  23. // a successful hydration.
  24. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
  25. oldVnode.removeAttribute(SSR_ATTR)
  26. hydrating = true
  27. }
  28. if (isTrue(hydrating)) {
  29. if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
  30. invokeInsertHook(vnode, insertedVnodeQueue, true)
  31. return oldVnode
  32. } else if (process.env.NODE_ENV !== 'production') {
  33. warn(
  34. 'The client-side rendered virtual DOM tree is not matching ' +
  35. 'server-rendered content. This is likely caused by incorrect ' +
  36. 'HTML markup, for example nesting block-level elements inside ' +
  37. '<p>, or missing <tbody>. Bailing hydration and performing ' +
  38. 'full client-side render.'
  39. )
  40. }
  41. }
  42. // either not server-rendered, or hydration failed.
  43. // create an empty node and replace it
  44. oldVnode = emptyNodeAt(oldVnode)
  45. }
  46. // replacing existing element
  47. const oldElm = oldVnode.elm
  48. const parentElm = nodeOps.parentNode(oldElm)
  49. // create new node
  50. createElm(
  51. vnode,
  52. insertedVnodeQueue,
  53. // extremely rare edge case: do not insert if old element is in a
  54. // leaving transition. Only happens when combining transition +
  55. // keep-alive + HOCs. (#4590)
  56. oldElm._leaveCb ? null : parentElm,
  57. nodeOps.nextSibling(oldElm)
  58. )
  59. // update parent placeholder node element, recursively
  60. if (isDef(vnode.parent)) {
  61. let ancestor = vnode.parent
  62. const patchable = isPatchable(vnode)
  63. while (ancestor) {
  64. for (let i = 0; i < cbs.destroy.length; ++i) {
  65. cbs.destroy[i](ancestor)
  66. }
  67. ancestor.elm = vnode.elm
  68. if (patchable) {
  69. for (let i = 0; i < cbs.create.length; ++i) {
  70. cbs.create[i](emptyNode, ancestor)
  71. }
  72. // #6513
  73. // invoke insert hooks that may have been merged by create hooks.
  74. // e.g. for directives that uses the "inserted" hook.
  75. const insert = ancestor.data.hook.insert
  76. if (insert.merged) {
  77. // start at index 1 to avoid re-invoking component mounted hook
  78. for (let i = 1; i < insert.fns.length; i++) {
  79. insert.fns[i]()
  80. }
  81. }
  82. } else {
  83. registerRef(ancestor)
  84. }
  85. ancestor = ancestor.parent
  86. }
  87. }
  88. // destroy old node
  89. if (isDef(parentElm)) {
  90. removeVnodes([oldVnode], 0, 0)
  91. } else if (isDef(oldVnode.tag)) {
  92. invokeDestroyHook(oldVnode)
  93. }
  94. }
  95. }
  96. invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  97. return vnode.elm
  98. }

(2)patchVnode

  1. function patchVnode (
  2. oldVnode,
  3. vnode,
  4. insertedVnodeQueue,
  5. ownerArray,
  6. index,
  7. removeOnly
  8. ) {
  9. if (oldVnode === vnode) { // 如果相同直接return
  10. return
  11. }
  12. if (isDef(vnode.elm) && isDef(ownerArray)) {
  13. // clone reused vnode
  14. vnode = ownerArray[index] = cloneVNode(vnode)
  15. }
  16. const elm = vnode.elm = oldVnode.elm
  17. if (isTrue(oldVnode.isAsyncPlaceholder)) {
  18. if (isDef(vnode.asyncFactory.resolved)) {
  19. hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
  20. } else {
  21. vnode.isAsyncPlaceholder = true
  22. }
  23. return
  24. }
  25. // reuse element for static trees.
  26. // note we only do this if the vnode is cloned -
  27. // if the new node is not cloned it means the render functions have been
  28. // reset by the hot-reload-api and we need to do a proper re-render.
  29. if (isTrue(vnode.isStatic) &&
  30. isTrue(oldVnode.isStatic) &&
  31. vnode.key === oldVnode.key &&
  32. (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  33. ) {
  34. vnode.componentInstance = oldVnode.componentInstance
  35. return
  36. }
  37. let i
  38. const data = vnode.data
  39. if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
  40. i(oldVnode, vnode)
  41. }
  42. // 进行子节点的比较
  43. const oldCh = oldVnode.children
  44. const ch = vnode.children
  45. if (isDef(data) && isPatchable(vnode)) {
  46. for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
  47. if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  48. }
  49. if (isUndef(vnode.text)) {
  50. if (isDef(oldCh) && isDef(ch)) {// 如果两个都有子节点的情况
  51. // 比较它们的子节点
  52. if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
  53. } else if (isDef(ch)) { // 如果只有新的有子节点
  54. if (process.env.NODE_ENV !== 'production') {
  55. checkDuplicateKeys(ch) // 就把新增的子节点插入到旧的节点中
  56. }
  57. if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
  58. addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
  59. } else if (isDef(oldCh)) { // 如果只有老的有子节点
  60. removeVnodes(oldCh, 0, oldCh.length - 1) // 就会把旧的节点直接删除
  61. } else if (isDef(oldVnode.text)) { // 如果旧节点有子文本节点
  62. nodeOps.setTextContent(elm, '') // 就把elm置空
  63. }
  64. } else if (oldVnode.text !== vnode.text) {
  65. nodeOps.setTextContent(elm, vnode.text)
  66. }
  67. if (isDef(data)) {
  68. if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
  69. }
  70. }

(3)updateChildren(双指针)

  1. function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  2. // 做了四对指针
  3. let oldStartIdx = 0
  4. let newStartIdx = 0
  5. let oldEndIdx = oldCh.length - 1
  6. let oldStartVnode = oldCh[0]
  7. let oldEndVnode = oldCh[oldEndIdx]
  8. let newEndIdx = newCh.length - 1
  9. let newStartVnode = newCh[0]
  10. let newEndVnode = newCh[newEndIdx]
  11. let oldKeyToIdx, idxInOld, vnodeToMove, refElm
  12. // removeOnly is a special flag used only by <transition-group>
  13. // to ensure removed elements stay in correct relative positions
  14. // during leaving transitions
  15. const canMove = !removeOnly
  16. if (process.env.NODE_ENV !== 'production') {
  17. checkDuplicateKeys(newCh)
  18. }
  19. while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  20. if (isUndef(oldStartVnode)) {
  21. oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
  22. } else if (isUndef(oldEndVnode)) {
  23. oldEndVnode = oldCh[--oldEndIdx]
  24. } else if (sameVnode(oldStartVnode, newStartVnode)) {
  25. patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
  26. oldStartVnode = oldCh[++oldStartIdx]
  27. newStartVnode = newCh[++newStartIdx]
  28. } else if (sameVnode(oldEndVnode, newEndVnode)) {
  29. patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
  30. oldEndVnode = oldCh[--oldEndIdx]
  31. newEndVnode = newCh[--newEndIdx]
  32. } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
  33. patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
  34. canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
  35. oldStartVnode = oldCh[++oldStartIdx]
  36. newEndVnode = newCh[--newEndIdx]
  37. } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
  38. patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
  39. canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
  40. oldEndVnode = oldCh[--oldEndIdx]
  41. newStartVnode = newCh[++newStartIdx]
  42. } else {
  43. // 在旧的节点中找索引
  44. if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
  45. idxInOld = isDef(newStartVnode.key)
  46. ? oldKeyToIdx[newStartVnode.key]
  47. : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
  48. if (isUndef(idxInOld)) { // New element
  49. // 如果找不到直接进行插入
  50. createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
  51. } else {
  52. vnodeToMove = oldCh[idxInOld]
  53. // 如果找的到就看他是否相等,如果相等就直接进行移动
  54. if (sameVnode(vnodeToMove, newStartVnode)) {
  55. patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
  56. oldCh[idxInOld] = undefined
  57. canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
  58. } else {
  59. // same key but different element. treat as new element
  60. // 如果索引相同,但元素不同,会重新创建一个插入到旧的节点中
  61. createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
  62. }
  63. }
  64. newStartVnode = newCh[++newStartIdx]
  65. }
  66. }
  67. if (oldStartIdx > oldEndIdx) {
  68. refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
  69. addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  70. } else if (newStartIdx > newEndIdx) {
  71. removeVnodes(oldCh, oldStartIdx, oldEndIdx)
  72. }
  73. }

图:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDcyMDg2_size_16_color_FFFFFF_t_70 1

发表评论

表情:
评论列表 (有 0 条评论,407人围观)

还没有评论,来说两句吧...

相关阅读