IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 《Vue.js 技术与实现》第 11 章 快速 diff 阅读总结 -> 正文阅读

[JavaScript知识库]《Vue.js 技术与实现》第 11 章 快速 diff 阅读总结

第 11 章主要讲解了 Vue3 在 更新子节点的实现原理。实现原理上使用了快速 diff,它源起于 ivi 和 inferno 这两个框架,Vue 借鉴并扩展它。

本章主要解决如下问题:

  • 处理相同的前置元素
  • 处理相同的后置元素
  • 判断是否需要进行DOM移动操作
  • 如何移动 DOM 元素

处理相同的前置元素

function patchKeyedChildren(n1, n2, container) {
  const newChildren = n2.children;
  const oldChildren = n1.children;

  // 更新相同的前缀节点
  // 索引 j 指向新旧两组子节点的开头
  let j = 0;
  let oldVNode = oldChildren[j];
  let newVNode = newChildren[j];

  // while 循环向后遍历,直到遇到拥有不同 key 值的节点为止
  while (oldVNode.key === newVNode.key) {
    // 调用 patch 函数更新
    patch(oldVNode, newVNode, container);

    j++;

    oldVNode = oldChildren[j];
    newVNode = newChildren[j];
  }
}

处理相同的前置元素时,使用 while 循环向后遍历,让索引 i 递增,直至遇到不同的节点为止。

这里需要注意的是,需要相同 key 值得节点,要调用 patch 函数进行更新。

处理相同的后置元素

// while 循环向前遍历,直到遇到拥有不同 key 值的节点为止
while (oldVNode.key === newVNode.key) {
  // 调用 patch 函数更新
  patch(oldVNode, newVNode, container);
  oldEnd--;
  newEnd--;
  oldVNode = oldChildren[oldEnd];
  newVNode = newChildren[newEnd];
}

在处理完相同前置元素后,再处理相同的后置元素,使用 while 循环向前遍历,不断移动新旧节点尾指针 newEndoldEnd 直到遇到拥有不同 key 值的节点为止。

这里需要注意的是,需要相同 key 值得节点,要调用 patch 函数进行更新。

剩下的新子节点里面新增的节点或者删除的节点,我们接下来继续处理

处理新增和删除的节点

// 满足条件,则说明从 j -> newEnd 之间的节点应作为新节点插入
if (j > oldEnd && j <= newEnd) {
  // 锚点的索引
  const anchorIndex = newEnd + 1;
  // 锚点元素
  const anchor =
    anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null;
  // 采用 while 循环,调用 patch 函数逐个挂载新增的节点
  while (j <= newEnd) {
    patch(null, newChildren[j++], container, anchor);
  }
} else if (j > newEnd && j <= oldEnd) {
  // j -> oldEnd 之间的节点应该被卸载
  while (j <= oldEnd) {
    unmount(oldChildren[j++]);
  }
}

j > oldEnd && j <= newEnd ,代表旧的子节点已经遍历完,新的子节点还有,说明存在新增,需要使用 while 循环挂载 jnewEnd 之间所有的子节点。新增的子节点以下一个子节点作为锚点元素,挂载在下一个锚点元素前面,如果没有下一个锚点元素,说明是当前节点就是最后一个。

j > newEnd && j <= oldEnd,代表新的子节点已经遍历完,旧的子节点还有,说明存在删除,需要使用 while 循环卸载 j 到 oldEnd 之间所有的子节点。

判断是否需要进行DOM移动操作和如何移动?

经过处理完相同前置元素 和 处理完相同后置元素后还有中间部分的节点无法处理,我们中间肯还存在元素无法进行处理,比如如下新旧子节点结构,在处理完前置和后置元素后中间还有一些差异的子节点需要 patch 更新的。

新子节点           旧子节点
p - 1							p - 1
----------------------------
p - 3							p - 2
p - 4							p - 3
p - 2							p - 4
p - 7							p - 6
----------------------------
p - 5							p - 5

接下来我们需要解决的问题就是这些节点

  • 判断是否需要移动
  • 如何移动
  • 找出那些需要新增或删除的节点

定义 source 数组保存新节点在旧节点中的下标

source 数组用于存储新子节点在旧子节点中的位置索引,后续将使用它计算出一个最长递增子序列,并用于辅助完成 DOM 的移动操作。

这个 source 变量在源码中命名是 newIndexToOldIndexMap

  1. 新增一个 source 数组,长度为新子节点预处理之后的节点数量,初始值填充为 -1,用来存储新子节点在旧子节点中的位置索引
  2. 为新节点建立一个索引表,保存节点 key 与位置索引之间的映射
  3. 遍历旧节点,旧节点的 key 在索引表中查找旧节点在新节点的索引,保存在 k 中
  4. 如果存在 k,说明旧节点在新节点中找到了,调用 patch 更新,填充 source 数组,否则直接卸载。
// 构造 source 数组
const count = newEnd - j + 1; // 新的一组子节点中剩余未处理节点的数量
const source = new Array(count);
source.fill(-1);

const oldStart = j;
const newStart = j;
// 索引表
const keyIndex = {};

for (let i = newStart; i <= newEnd; i++) {
  keyIndex[newChildren[i].key] = i;
}

for (let i = oldStart; i <= oldEnd; i++) {
  oldVNode = oldChildren[i];
  // k 是旧节点在新节点中的索引
  const k = keyIndex[oldVNode.key];

  if (typeof k !== 'undefined') {
    newVNode = newChildren[k];
    patch(oldVNode, newVNode, container);
    source[k - newStart] = i;
  } else {
    // 没找到
    unmount(oldVNode);
  }
}

判断是否需要进行DOM移动操作

const count = newEnd - j + 1; // 新的一组子节点中剩余未处理节点的数量
let moved = false;
let pos = 0;
let patched = 0;

for (let i = oldStart; i <= oldEnd; i++) {
  oldVNode = oldChildren[i];
  if (patched < count) {
    const k = keyIndex[oldVNode.key];
    if (typeof k !== 'undefined') {
      newVNode = newChildren[k];
      patch(oldVNode, newVNode, container);
      patched++;
      source[k - newStart] = i;
      // 判断是否需要移动
      if (k < pos) {
        moved = true;
      } else {
        pos = k;
      }
    } else {
      // 没找到
      unmount(oldVNode);
    }
  } else {
    unmount(oldVNode);
  }
}
  • 定义变量 moved 标识是否需要移动,pos 表示遍历旧子节点过程中遇到的最大索引值
  • 如果遍历过程遇到的索引值呈递增趋势,说明不需要节点移动,否则需要。有了 moved 标识,就知道了是否需要移动节点
  • 定义变量 patched 表示已更新节点数量,如果更新节点数量大于新的一组节点需要更新的数量,说明有多余的节点,需要卸载

如何移动?

if (moved) {
  const seq = getRequence(source);
  // s 指向最长递增子序列的最后一个值
  let s = seq.length - 1;
  let i = count - 1;
  for (i; i >= 0; i--) {
    if (source[i] === -1) {
      // 说明索引为 i 的节点是全新的节点,应该将其挂载
      // 该节点在新 children 中的真实位置索引
      const pos = i + newStart;
      const newVNode = newChildren[pos];
      // 该节点下一个节点的位置索引
      const nextPos = pos + 1;
      // 锚点
      const anchor =
        nextPos < newChildren.length ? newChildren[nextPos].el : null;
      // 挂载
      patch(null, newVNode, container, anchor);
    } else if (i !== seq[s]) {
      // 说明该节点需要移动
      // 该节点在新的一组子节点中的真实位置索引
      const pos = i + newStart;
      const newVNode = newChildren[pos];
      // 该节点下一个节点的位置索引
      const nextPos = pos + 1;
      // 锚点
      const anchor =
        nextPos < newChildren.length ? newChildren[nextPos].el : null;
      // 移动
      insert(newVNode.el, container, anchor);
    } else {
      // 当 i === seq[j] 时,说明该位置的节点不需要移动
      // 并让 s 指向下一个位置
      s--;
    }
  }
}

通过 getRequence 获取 source 数组的最长递增子序列,找到不需要移动的节点

关于最长递增子序列看下我这篇文章:Vue3 最长递增子序列详解

seq  source    KeyIndex     新节点       旧节点
                            
0  ↑   2          3:1       p - 3       p - 2
1 (s)  3          4:2       p - 4       p - 3
       1          2:3       p - 2 ↑     p - 4
      -1          7:4       p - 7(i)    p - 6

倒序遍历节点,如果 source[i] 为 默认值 -1,说明该节点时新增的节点,需要挂载。

如果遍历的节点下标不是最长递增子序列 seq[s] 说明是需要移动的节点,设置锚点为上一个已经更新的节点为锚点位置,将当前节点移动到锚点前面。

如果遍历的节点下标是最长递增子序列 seq[s] 说明是不需要移动节点,s— 即可

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 12:02:05  更:2022-04-04 12:04:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:48:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码