文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

vue3 diff算法怎么应用

2023-07-02 18:18

关注

这篇文章主要介绍“vue3 diff算法怎么应用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“vue3 diff算法怎么应用”文章能帮助大家解决问题。

一、可能性(常见):

旧的:a b c
新的:a b c d

旧的:  a b c
新的:d a b c

旧的:a b c d
新的:a b c

旧的:d a b c
新的:  a b c

旧的:a b c d e i f g
新的:a b e c d h f g

对应的真实虚拟节点(为方便理解,文中用字母代替):

// vnode对象const a = {  type: 'div', // 标签  props: {style: {color: 'red'}}, // 属性  children: [], // 子元素  key: 'key1', // key  el: '<div ></div>', // 真实dom节点  ...}

二、找规律

去掉前面和后面相同的部分

// c1表示旧的子节点,c2表示新的子节点const patchKeyedChildren = (c1, c2) => {  let i = 0  let e1 = c1.length - 1  let e2 = c2.length - 1  // 从前面比  while (i <= e1 && i <= e2) {    const n1 = c1[i]    const n2 = c2[i]    // 标签和key是否相同    // if (n1.type === n2.type && n1.key === n2.key)    if (n1 === n2) {      // 继续对比其属性和子节点    } else {      break    }    i++  }  // 从后面比  while (i <= e1 && i <= e2) {    const n1 = c1[e1]    const n2 = c2[e2]    // 标签和key是否相同    // if (n1.type === n2.type && n1.key === n2.key)    if (n1 === n2) {      // 继续对比其属性和子节点    } else {      break    }    e1--    e2--  }  console.log(i, e1, e2)}// 调用示例patchKeyedChildren(['a', 'b', 'c', 'd'], ['a', 'b', 'c'])

通过这个函数可以得到:

旧的:a b c
新的:a b c d

i = 3  e1 = 2  e2 = 3

旧的:  a b c
新的:d a b c

i = 0  e1 = -1  e2 = 0

旧的:a b c d
新的:a b c

i = 3  e1 = 3  e2 = 2

旧的:d a b c
新的:  a b c

i = 0  e1 = 0  e2 = -1

旧的:a b c d e i f g
新的:a b e c d h f g

i = 2  e1 = 5  e2 = 5

扩展:

旧的:a b c
新的:a b c d e f
i = 3  e1 = 2  e2 = 5

旧的:a b c
新的:a b c
i = 3  e1 = 2  e2 = 2

旧的:e d a b c
新的:    a b c
i = 0  e1 = 1  e2 = -1

旧的:c d e  
新的:e c d h
i = 0  e1 = 2  e2 = 3

从上面结果中我们可以找到规律:

// 当i大于e1时if (i > e1) {  if (i <= e2) {    while (i <= e2) {      const nextPos = e2 + 1      const anchor = nextPos < c2.length ? c2[nextPos].el : null      // 添加新的子节点c2[i]在anchor的前面      // todo      i++    }  }}// 当i大于e2时else if (i > e2) {  if (i <= e1) {    while (i <= e1) {      // 删除旧的子节点c1[i]      // todo      i++    }  }}
// 其它let s1 = ilet s2 = i// 以新的子节点作为参照物const keyToNewIndexMap = new Map()for (let i = s2; i <= e2; i++) {  // 节点的key做为唯一值  // keyToNewIndexMap.set(c2[i].key, i)  keyToNewIndexMap.set(c2[i], i)}// 新的总个数const toBePatched = e2 - s2 + 1// 记录新子节点在旧子节点中的索引const newIndexToOldIndexMap = new Array(toBePatched).fill(0)// 循环老的子节点for (let i = s1; i <= e1; i++) {  const oldChild = c1[i]  // let newIndex = keyToNewIndexMap.get(oldChild.key)  let newIndex = keyToNewIndexMap.get(oldChild)  // 在新子节点中不存在  if (newIndex === undefined) {    // 删除oldChild    // todo  } else {    newIndexToOldIndexMap[newIndex - s2] = i + 1 // 永远不会等于0, 这样0就可以表示需要创建了    // 继续对比其属性和子节点    // todo  }}console.log(newIndexToOldIndexMap)// 需要移动位置for (let i = toBePatched - 1; i >= 0; i--) {  let index = i + s2  let current = c2[index]  let anchor = index + 1 < c2.length ? c2[index + 1].el : null  if (newIndexToOldIndexMap[i] === 0) {    // 在anchor前面插入新的节点current    // todo  } else {    // 在anchor前面插入对应旧节点.el,current.el元素等于对应的旧节点.el(在其它代码中赋值了)    // todo  }}

第1种和第2种比较简单,不做过多的讲解,我们来看看第3种,以下面为例

序号: 0 1  2 3 4 5  6 7
旧的:(a b) c d e i (f g)
新的:(a b) e c d h (f g)

标记:       4+1 2+1 3+1  0
新的:(...)   e   c   d   h (...)

到目的为止,新旧元素的更替已经全部完成,但大家有没有发现一个问题,e c d h四个元素都移动了一次,我们可以看出如果只移动e和创建h,c和d保持不变,效率会更高

三、算法优化

序号: 0 1  2 3 4 5  6 7
旧的:(a b) c d e i (f g)
新的:(a b) e c d h (f g)
对应的标记是[5, 3, 4, 0]

序号:0 1 2 3 4 5
旧的:c d e i f g
新的:e c d f g j
对应的标记是[3, 1, 2, 5, 6, 0]

从上面两个例子中可以看出:
第1个的最优解是找到c d,只需移动e,创建h
第2个的最优解是找到c d f g,只需移动e,创建j

过程:

注意0表示直接创建,不参与计算

例子:

// 需要移动位置// 找出最长的递增子序列对应的索引值,如:[5, 3, 4, 0] -> [1, 2]let increment = getSequence(newIndexToOldIndexMap)console.log(increment)let j = increment.length - 1for (let i = toBePatched - 1; i >= 0; i--) {  let index = i + s2  let current = c2[index]  let anchor = index + 1 < c2.length ? c2[index + 1].el : null  if (newIndexToOldIndexMap[i] === 0) {    // 在anchor前面插入新的节点current    // todo  } else {    if (i !== increment[j]) {      // 在anchor前面插入对应旧节点.el,current.el元素等于对应的旧节点.el(在其它代码中赋值了)      // todo    } else { // 不变      j--    }  }}

最长的递增子序列

// 最长的递增子序列,https://en.wikipedia.org/wiki/Longest_increasing_subsequencefunction getSequence(arr) {  const len = arr.length  const result = [0] // 以第一项为基准  const p = arr.slice() // 标记索引,slice为浅复制一个新的数组  let resultLastIndex  let start  let end  let middle  for (let i = 0; i < len; i++) {    let arrI = arr[i]    if (arrI !== 0) { // vue中等于0,表示需要创建      resultLastIndex = result[result.length - 1]      // 插到末尾      if (arr[resultLastIndex] < arrI) {        result.push(i)        p[i] = resultLastIndex // 前面的那个是谁        continue      }      // 递增序列,二分类查找      start = 0      end = result.length - 1      while(start < end) {        middle = (start + end) >> 1 // 相当于Math.floor((start + end)/2)        if (arr[result[middle]] < arrI) {          start = middle + 1        } else  {          end = middle        }      }      // 找到最近的哪一项比它大的,替换      if (arr[result[end]] > arrI) {        result[end] = i        if (end > 0) {          p[i] = result[end - 1] // 前面的那个是谁        }      }    }  }  let i = result.length  let last = result[i - 1]  while(i-- > 0) {    result[i] = last    last = p[last]  }  return result}console.log(getSequence([5, 3, 4, 0])) // [1, 2]console.log(getSequence([3, 1, 2, 5, 6, 0])) // [ 1, 2, 3, 4 ]

讲解后续补充...

完整代码

// 最长的递增子序列,https://en.wikipedia.org/wiki/Longest_increasing_subsequencefunction getSequence(arr) {  const len = arr.length  const result = [0] // 以第一项为基准  const p = arr.slice() // 标记索引,slice为浅复制一个新的数组  let resultLastIndex  let start  let end  let middle  for (let i = 0; i < len; i++) {    let arrI = arr[i]    if (arrI !== 0) { // vue中等于0,表示需要创建      resultLastIndex = result[result.length - 1]      // 插到末尾      if (arr[resultLastIndex] < arrI) {        result.push(i)        p[i] = resultLastIndex // 前面的那个是谁        continue      }      // 递增序列,二分类查找      start = 0      end = result.length - 1      while(start < end) {        middle = (start + end) >> 1 // 相当于Math.floor((start + end)/2)        if (arr[result[middle]] < arrI) {          start = middle + 1        } else  {          end = middle        }      }      // 找到最近的哪一项比它大的,替换      if (arr[result[end]] > arrI) {        result[end] = i        if (end > 0) {          p[i] = result[end - 1] // 前面的那个是谁        }      }    }  }  let i = result.length  let last = result[i - 1]  while(i-- > 0) {    result[i] = last    last = p[last]  }  return result}// c1表示旧的子节点,c2表示新的子节点const patchKeyedChildren = (c1, c2) => {  let i = 0  let e1 = c1.length - 1  let e2 = c2.length - 1  // 从前面比  while (i <= e1 && i <= e2) {    const n1 = c1[i]    const n2 = c2[i]    // 标签和key是否相同    // if (n1.type === n2.type && n1.key === n2.key)    if (n1 === n2) {      // 继续对比其属性和子节点    } else {      break    }    i++  }  // 从后面比  while (i <= e1 && i <= e2) {    const n1 = c1[e1]    const n2 = c2[e2]    // 标签和key是否相同    // if (n1.type === n2.type && n1.key === n2.key)    if (n1 === n2) {      // 继续对比其属性和子节点    } else {      break    }    e1--    e2--  }  console.log(i, e1, e2)  // 当i大于e1时  if (i > e1) {    if (i <= e2) {      while (i <= e2) {        const nextPos = e2 + 1        const anchor = nextPos < c2.length ? c2[nextPos].el : null        // 添加子节点c2[i]在anchor的前面        // todo        i++      }    }  }  // 当i大于e2时  else if (i > e2) {    if (i <= e1) {      while (i <= e1) {        // 删除子节点c1[i]        // todo        i++      }    }  }  // 其它  else {    let s1 = i    let s2 = i    // 以新的子节点作为参照物    const keyToNewIndexMap = new Map()    for (let i = s2; i <= e2; i++) {      // 节点的key做为唯一值      // keyToNewIndexMap.set(c2[i].key, i)      keyToNewIndexMap.set(c2[i], i)    }    // 新的总个数    const toBePatched = e2 - s2 + 1    // 记录新子节点在旧子节点中的索引    const newIndexToOldIndexMap = new Array(toBePatched).fill(0)    // 循环老的子节点    for (let i = s1; i <= e1; i++) {      const oldChild = c1[i]      // let newIndex = keyToNewIndexMap.get(oldChild.key)      let newIndex = keyToNewIndexMap.get(oldChild)      // 在新子节点中不存在      if (newIndex === undefined) {        // 删除oldChild        // todo      } else {        newIndexToOldIndexMap[newIndex - s2] = i + 1 // 永远不会等于0, 这样0就可以表示需要创建了        // 继续对比其属性和子节点        // todo      }    }    console.log(newIndexToOldIndexMap)    // 需要移动位置    // 找出最长的递增子序列对应的索引值,如:[5, 3, 4, 0] -> [1, 2]    let increment = getSequence(newIndexToOldIndexMap)    console.log(increment)    let j = increment.length - 1    for (let i = toBePatched - 1; i >= 0; i--) {      let index = i + s2      let current = c2[index]      let anchor = index + 1 < c2.length ? c2[index + 1].el : null      if (newIndexToOldIndexMap[i] === 0) {        // 在anchor前面插入新的节点current        // todo      } else {        if (i !== increment[j]) {          // 在anchor前面插入对应旧节点.el,current.el元素等于对应的旧节点.el(在其它代码中赋值了)          // todo        } else { // 不变          j--        }      }    }  }}// 调用示例patchKeyedChildren(['c', 'd', 'e', 'i', 'f', 'g'], ['e', 'c', 'd', 'f', 'g', 'j'])

关于“vue3 diff算法怎么应用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网行业资讯频道,小编每天都会为大家更新不同的知识点。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯