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

相关推荐
胡西风_foxww20 分钟前
【ES6复习笔记】Spread 扩展运算符(8)
前端·笔记·es6·扩展·运算符·spread
小林爱1 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
跨境商城搭建开发1 小时前
一个服务器可以搭建几个网站?搭建一个网站的流程介绍
运维·服务器·前端·vue.js·mysql·npm·php
hhzz1 小时前
vue前端项目中实现电子签名功能(附完整源码)
前端·javascript·vue.js
秋雨凉人心1 小时前
上传npm包加强
开发语言·前端·javascript·webpack·npm·node.js
NoneCoder1 小时前
CSS系列(37)-- Overscroll Behavior详解
前端·css
Nejosi_念旧2 小时前
使用Webpack构建NPM Library
前端·webpack·npm
前端切圖仔2 小时前
失业,仲裁,都赶上了(二)
前端·javascript·程序员
冰红茶-Tea2 小时前
typescript数据类型(二)
前端·typescript
slongzhang_2 小时前
elementPlus消息组件多按钮案例
前端·javascript·vue.js