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

相关推荐
yzzzzzzzzzzzzzzzzz5 分钟前
初识javascript
前端·javascript
excel1 小时前
硬核 DOM2/DOM3 全解析:从命名空间到 Range,前端工程师必须掌握的底层知识
前端
专注API从业者8 小时前
Python + 淘宝 API 开发:自动化采集商品数据的完整流程
大数据·运维·前端·数据挖掘·自动化
烛阴9 小时前
TypeScript高手密技:解密类型断言、非空断言与 `const` 断言
前端·javascript·typescript
样子201810 小时前
Uniapp 之renderjs解决swiper+多个video卡顿问题
前端·javascript·css·uni-app·html
Nicholas6810 小时前
flutterAppBar之SystemUiOverlayStyle源码解析(一)
前端
黑客飓风10 小时前
JavaScript 性能优化实战大纲
前端·javascript·性能优化
emojiwoo12 小时前
【前端基础知识系列六】React 项目基本框架及常见文件夹作用总结(图文版)
前端·react.js·前端框架
张人玉12 小时前
XML 序列化与操作详解笔记
xml·前端·笔记
杨荧12 小时前
基于Python的宠物服务管理系统 Python+Django+Vue.js
大数据·前端·vue.js·爬虫·python·信息可视化