《Vue.js设计与实现》第9章-简单Diff算法

9-1 减少DOM操作的性能开销

js 复制代码
// 旧vnode
const oldVNode = {
  type: 'div',
  children: [
    { type: 'p', children: '1' },
    { type: 'p', children: '2' },
    { type: 'p', children: '3' },
  ]
}

// 新vnode
const newVNode = {
  type: 'div',
  children: [
    { type: 'p', children: '4' },
    { type: 'p', children: '5' },
    { type: 'p', children: '6' },
  ]
}

按照之前的做法,当更新子节点时,需要执行6次DOM操作:

  1. 卸载所有旧节点,需要3次DOM删除操作;
  2. 挂载所有新节点,需要3次DOM添加操作。

所以更理想的更新方式呢?

重新实现两组子节点的更新逻辑,如下面patchChildren函数:

js 复制代码
function patchChildren(n1, n2, container){
  if(typeof n2.children === 'string'){
    // 省略部分代码
  } else if(Array.isArray(n2.children)){
    // 新旧children
    
    // 遍历旧的children
    // 调用patch函数逐个更新子节点
  } else {
    // 省略部分代码
  }
}

图9-1 仅更新文本子节点

图9-2 卸载已经不存在的节点

图9-3 挂载新的节点

这时修改patchChildren方法:

js 复制代码
function patchChildren(n1, n2, container){
  if(typeof n2.children === 'string'){
    // 省略部分代码
  } else if(Array.isArray(n2.children)){
    // 旧的一组子节点的长度
    
    // 新的一组子节点的长度
    
    // 两组子节点的公共长度commonLength,即两者中较短的那一组子节点的长度
    
    // 遍历commonLength次,调用patch
    
    // 如果newLen > oldLen,说明有新子节点需要挂载
    
    // 如果oldLen > newLen,说明有旧子节点需要卸载
  } else {
    // 省略部分代码
  }
}

9.2 DOM 复用与key的作用

js 复制代码
// oldChildren
[
  { type: 'p' },
  { type: 'div' },
  { type: 'span' },
]

// newChildren
[
  { type: 'span' },
  { type: 'p' },
  { type: 'div' },
]

观察新旧两组子节点,二者只是顺序不同。我们怎么判断新的一组子节点的第一个子节点{ type: 'span' }和旧的一组子节点中的第3个子节点相同呢?

是否可用vnode.type的值来判断呢?

是否有其他判断的方式呢?

图9-4 有key与无key

DOM可复用那它还需要更新吗?

js 复制代码
const oldVNode = { type: 'p', key: 1, children: 'text 1' }
const newVNode = { type: 'p', key: 1, children: 'text 2' }

接着修改patchChildren函数:

js 复制代码
function patchChildren(n1, n2, container){
  if(typeof n2.children === 'string'){
    // 省略部分代码
  } else if(Array.isArray(n2.children)){
    
    // 遍历新的children
    // 遍历旧的children
    // 如果找到了具有相同key值的两个节点,说明可复用,但仍然需要调用patch函数更新
  } else {
    // 省略部分代码
  }
}

9-3 找到需要移动的元素

上一节我们能通过key值找到可复用的节点。接下来需要思考,如何判断一个节点是否需要移动,以及如何移动。

图9-5 节点顺序不变

寻找节点过程中,得到位置索引序列是多少?

图9-6 节点顺序变化

寻找节点过程中,得到位置索引序列是多少?

结论是什么?

我们可以用lastIndex变量存储整个寻找过程中遇到的最大索引值。

js 复制代码
function patchChildren(n1, n2, container){
  if(typeof n2.children === 'string'){
    // 省略部分代码
  } else if(Array.isArray(n2.children)){
    // 用lastIndex存储最大索引
    
    // 遍历新子节点
      // 遍历旧子节点
    
        // 如果他们的key相同 -> patch
          // 如果当前找到的节点在旧children中的索引值 < lastIndex 
          // 说明该节点对应的真实DOM需要移动
          
          // 如果当前找到的节点在旧children中的索引值 >= lastIndex 
          // 则更新lastIndex的值
          
  } else {
    // 省略部分代码
  }
}

9.4 如何移动元素

移动节点指的是,移动一个虚拟节点所对应的真实DOM节点,并不是移动虚拟节点本身。如何获取到真实的DOM节点呢?

回顾一下patchElement函数的代码。

js 复制代码
function patchElement(n1, n2){
  // 新的vnode也引用了真实DOM元素
  const el = n2.el = n1.el
  // 省略部分代码
}

图9-8 使新的子节点也引用真实DOM元素

它的更新步骤:

1.取新的一组子节点中第一个节点p-3。尝试在旧的一组子节点中找到具有相同key值的可复用节点。所以节点p-3对应的真实DOM需要移动吗?

2.取新的一组子节点中第二个节点p-1。尝试在旧的一组子节点中找到具有相同key值的可复用节点。所以节点p-1对应的真实DOM需要移动吗?

3.取新的一组子节点中第三个节点p-2。尝试在旧的一组子节点中找到具有相同key值的可复用节点。所以节点p-2对应的真实DOM需要移动吗?

请根据图9-8 画出更新之后的图。

接着我们着手实现代码。patchChildren函数的流程图如下:

9-5 添加新元素

图9-12 新增节点p-4 对于新增节点,在更新时我们应该正确地将它挂载,这主要分为两步:

  • 想办法找到新增节点;
  • 将新增节点挂载到正确位置;

我们如何找到新增节点?

图9-13 新旧两组子节点与真实DOM元素的当前状态

我们先模拟执行简单Diff算法的更新逻辑。 1.取新的一组子节点中第一个节点p-3。在旧的一组子节点中寻找可复用的节点。所以节点p-3对应的真实DOM需要移动吗?

2.取新的一组子节点中第二个节点p-1。在旧的一组子节点中寻找可复用的节点。所以节点p-1对应的真实DOM需要移动吗?

3.取新的一组子节点中第三个节点p-4。在旧的一组子节点中寻找可复用的节点。所以节点p-4对应的真实DOM存在吗?

4.取新的一组子节点中第四个节点p-2。在旧的一组子节点中寻找可复用的节点。所以节点p-2对应的真实DOM需要移动吗?

请画出更新之后的图。

patchChildren函数代码的流程图如下:

现在的patch函数还不支持传递第四个参数,即锚点元素。

// 这个后面补充

9-6 移除不存在的元素

图9-18 新旧两组子节点与真实DOM节点的当前状态

接着,我们开始模拟执行更新的过程。

1.取新的一组子节点中第一个节点p-3。在旧的一组子节点中寻找可复用的节点。所以节点p-3对应的真实DOM需要移动吗?

1.取新的一组子节点中第二个节点p-1。在旧的一组子节点中寻找可复用的节点。所以节点p-1对应的真实DOM需要移动吗?

更新结束之后,我们发现,节点p-2对应的真实DOM仍然存在。所以需要增加额外的逻辑来删除遗留节点。

相关推荐
你的人类朋友29 分钟前
🫏光速入门cURL
前端·后端·程序员
01传说1 小时前
vue3 配置安装 pnpm 报错 已解决
java·前端·vue.js·前端框架·npm·node.js
烛阴2 小时前
Python装饰器解除:如何让被装饰的函数重获自由?
前端·python
千鼎数字孪生-可视化2 小时前
Web技术栈重塑HMI开发:HTML5+WebGL的轻量化实践路径
前端·html5·webgl
凌辰揽月2 小时前
7月10号总结 (1)
前端·css·css3
天天扭码3 小时前
很全面的前端面试——CSS篇(上)
前端·css·面试
EndingCoder3 小时前
搜索算法在前端的实践
前端·算法·性能优化·状态模式·搜索算法
sunbyte3 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DoubleVerticalSlider(双垂直滑块)
前端·javascript·css·vue.js·vue
Favor_Yang3 小时前
SQL Server通过存储过程实现HTML页面生成
前端·信息可视化·sqlserver·存储过程
中微子4 小时前
JavaScript事件循环机制:面试官最爱问的10个问题详解
前端