《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仍然存在。所以需要增加额外的逻辑来删除遗留节点。

相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax