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

相关推荐
Huazzi.18 分钟前
免费好用的静态网页托管平台全面对比介绍
前端·网络·github·web
吃土少女古拉拉26 分钟前
前端和后端
前端·学习笔记
寒雒1 小时前
【Python】实战:实现GUI登录界面
开发语言·前端·python
独上归州1 小时前
Vue与React的Suspense组件对比
前端·vue.js·react.js·suspense
Komorebi⁼2 小时前
Vue核心特性解析(内含实践项目:设置购物车)
前端·javascript·vue.js·html·html5
明月清风徐徐2 小时前
Vue实训---0-完成Vue开发环境的搭建
前端·javascript·vue.js
SameX2 小时前
HarmonyOS Next 企业数据备份与恢复策略
前端·harmonyos
SameX2 小时前
HarmonyOS Next 企业数据传输安全策略
前端·harmonyos
daopuyun2 小时前
LoadRunner小贴士|开发Web-HTTP/HTML协议HTML5相关视频应用测试脚本的方法
前端·http·html
李先静2 小时前
AWTK-WEB 快速入门(1) - C 语言应用程序
c语言·开发语言·前端