详解vue中diff算法

2023-11-04

vue中diff算法

本质:找出两个对象之间的差异
核心:子节点数组进行比较(首尾两端对比)

作用

渲染真实的DOM的开销很大,如果修改了某个数据直接渲染到真实DOM上会引起整个DOM树的重绘和重排。diff算法旨在只更新修改的一小块DOM,而不是整个DOM。
找出两个对象之间的差异,目的是尽可能的做到节点之间的复用

1.真实DOM和虚拟DOM

当数据发生改变时: 根据真实DOM生成一颗 virtual DOM ,当 virtual DOM 某个节点的数据改变后会生成一个新的 Vnode ,然后 Vnode 和 oldVnode 作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使 oldVnode 的值为 Vnode 。

例如下方是一个真实dom节点

<div>
	<p class="node">123</p>
</div>

可见,一个dom节点主要包含自身标签名、自身的属性、子节点三部分。

对应的virtual DOM:

var Vnode = {
 tag: 'div',
 children: [
  { tag: 'p', text: '123', attr:{class:'node'} }
 ]
};

virtual DOM是将真实的DOM的数据抽取出来,以对象的形式模拟树形结构。

2.diff的比较方式

diff在新旧节点比较时,只会同层级进行比较,不会跨级比较。例如:

<div>
 <p>123</p>
</div>
 
<div>
 <span>456</span>
</div>

上方代码会分别比较同层级的两个div和第二层的p和span标签,但是不会讲div和span相比较。在其他地方看到的一张很形象的图:
在这里插入图片描述

diff涉及到的函数

//1.判断两棵Virtual Dom Tree 差异
//2.把差异更新到真实Dom中去
let patchs = diff(oldTree, newTree);//获取两棵Virtual Dom Tree 差异
patch(ulDom, patchs);//找到对应的真实dom,进行部分渲染

diff函数调用流程:
在这里插入图片描述

//深度遍历树,将需要做变更操作的取出来
//局部更新 DOM
function patch(node,patchs){
    //代码略
}

// diff 入口,比较新旧两棵树的差异
function diff (oldTree, newTree) {
  let index   = 0
  let patches = {} // 记录每个节点差异的补丁
  dfs(oldTree, newTree, index, patches)
  return patches;
}
/**
 * dfs 深度遍历查找节点差异
 * @param  oldNode - 旧虚拟Dom树
 * @param  newNode - 新虚拟Dom树
 * @param  index - 当前所在树的第几层
 * @param  patches - 记录节点差异
 */
function dfs (oldNode, newNode, index, patches){
    let currentPatch = [];//当前层的差异对比
    if (!newNode) {
        //如果节点不存不用处理,listdiff函数会处理被删除的节点
    }else if (isTxt(oldNode) && isTxt(newNode)) {//isTxt用来判断是否是文本,为了简便这边并没有声明
        if (newNode !== oldNode) 
            currentPatch.push({ type: "text", content: newNode })
        //如果发现文本不同,currentPatch会记录一个差异 
    }else if(oldNode.tagName === newNode.tagName && oldNode.key === newNode.key){
        //如果发现两个节点一样 则去判断节点是属性是否一样,并记录下来
        let attrsPatches = diffAttrs(oldNode, newNode)
        if (attrsPatches) {//有属性差异则把差异记录下来
          currentPatch.push({ type: "attrs", "attrs": attrsPatches })
        }
        // 递归遍历子节点,并对子节点进行diff比较
        diffChildren(oldNode.children, newNode.children, index, patches)
    }else{
        //最后一种情况是,两个节点完全不一样,这样只需要把旧节点之间替换就行
        //把当前差异记录下来
        currentPatch.push({ type: "replace", node: newNode})
    }

    //如果有差异则记录到当前层去
    if (currentPatch.length) {
        if (patches[index]) {
          patches[index] = patches[index].concat(currentPatch)
        } else {
          patches[index] = currentPatch
        }
     }
}
//判断两个节点的属性差异
function diffAttrs(oldNode, newNode){
    let attrsPatches = {};//记录差异
    let count = 0;//记录差异的条数

    /**
    代码略
    判断两个节点的属性差异的代码就略了,
    让你们知道这里的代码就是判断两个节点的属性有哪些差异,
    如果有差异就记录在attrsPatches这个对象中,并把它返回
    **/
    if(0 == count){
        return null;
    }else {
       return attrsPatches; 
    }
}
//判断孩子节点
function diffChildren(oldChild, newChild, index, patches){
    let { changes, list } = listDiff(oldChild, newChild, index, patches);
    if (changes.length) {//如果有差异则记录到当前层去
        if (patches[index]) {
          patches[index] = patches[index].concat(changes);
        } else {
          patches[index] = changes;
        }
    }
    // 代码略
    //遍历当前数组
    oldChild && oldChild.forEach((item, i) => {
        // 代码略
        let node;// 经过判断后node节点是同时存在于oldChild 和 newChild中
        //则对节点进行递归遍历 相当于 进入下一层 节点,
        let curIndex;
        dfs(item, node, curIndex, patches);
        // 代码略
    })

}

//判断oldNodeList, newNodeList 节点的位置差,主要是为了判断哪些节点被移动、删除、新增。
function listDiff(oldNodeList, newNodeList, index){
    let changes = [];//记录 oldNodeList, newNodeList节点的位置差异,是被移动、删除、新增
    let list = [];//记录 oldNodeList,newNodeList 同时存在的节点
    /**
    具体判断逻辑的代码就略了
    **/
    return {changes,list};
}

总结

Virtual Dom 算法的实现主要是以下三步

通过 JS 来模拟生成 Virtual Dom Tree
判断两个 Tree 的差异
渲染差异

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

详解vue中diff算法 的相关文章

  • 单值或常量值时在 x 轴上绘制的样条图 - highchart

    while using the older version of highchart 2 1 6 if a plot had only one value or a series of same values it would plot a
  • 来自 DataURL 的 Blob?

    Using FileReader s readAsDataURL 我可以将任意数据转换为数据 URL 有没有办法将数据 URL 转换回Blob使用内置浏览器 API 的实例 用户 Matt 一年前提出了以下代码 如何在javascript中
  • 使用 NodeJS 让 Discord 机器人发送带有消息的图片

    我有几张照片 全部在 imgur 上 带有直接图像链接 格式 https i imgur com XXXXXX jpg https i imgur com XXXXXX jpg 以及用 NodeJS 制作的 Discord 机器人 我发送这
  • Chrome SuppressDifferentOriginSubframeJSDialogs 使用 JS 设置覆盖?

    Chrome 开发团队显然刚刚推出了一项名为 SuppressDifferentOriginSubframeJSDialogs 的新 功能 这使得默认情况下不会显示来自与父级不同域上的 iFrame 的警报 确认框 https www ch
  • ngModel.$parsers 忽略 ng-model 值末尾的空格

    我有这样的指令 directive noWhitespace parse function parse return restrict A require ngModel link function scope element attrs
  • 是否可以将反应组件导出为非反应项目中的函数

    有没有办法在非 React 项目中将 React 组件导出为函数并传入 props 作为函数的参数 我最近用 create react app 完成了一个 React 项目 现在我想将它用于其他非 React 项目 纯 Javascript
  • 是否可以覆盖 javaScript 原始数据类型?

    问题是不言自明的 我知道可以扩展原始数据类型 例如string但有可能覆盖它吗 这是在采访中被问到的一个问题 不 你不能覆盖任何东西 Ecma脚本定义原始类型 http es5 github com x4 3 2 Undefined Nul
  • 在 JavaScript 中比较表单中的两个数字

    当我尝试比较不同的数字时 数字发生变化 但文本部分保持不变 这只发生在较大 较小的情况下 而不会发生在 NaN 或相等的情况下 这是我的代码 function check var a document getElementById a va
  • JQuery DataTable 单元格从行单击

    我正在尝试在 jquery 数据表上实现一个函数 该函数返回单击行的第一列和第四列 我正在遵循这个示例 它允许我操作单击的行http datatables net examples api select single row html ht
  • 点击问题:动态生成的链接不触发点击功能

    下面是两个代码片段 由于某种原因什么也没有发生 但来自同一个 JS 文件的其他 jQuery 函数在带有 UL 的页面上执行得很好 这是在盯着我看吗 ul class paganation li 1 li li a href 2 a li
  • 在 vue.js 模板中包含外部脚本

    我是 Vue js 和 web pack 的新手 所以我决定使用 vue cli webpack 来构建初始应用程序 我试图包含一个外部脚本 例如组件 不需要的模板中 但是 Vue 警告这是不允许的 我的 index html 文件与最初生
  • 空 URL 哈希导致页面在 js 事件上跳转

    我有一个带有下一个和上一个按钮的照片库 如果我的某个 javascript 方法由于某种原因被破坏 那么当单击其中一个按钮时 它会向 url 添加一个哈希值 即 www google com 我知道可以给散列一个 div id 来跳转到页面
  • Web SQL 数据库 + Javascript 循环

    我正在尝试解决这个问题 但我自己似乎无法解决 我正在使用 Web SQL DB 但无法让循环正常使用它 I use for var i 0 i lt numberofArticles 1 i db transaction function
  • 使用 onBlur 事件上的值更新 React 输入文本字段

    我有以下输入字段 在模糊时 该函数调用服务来更新服务器的输入值 完成后 它会更新输入字段 我怎样才能让它发挥作用 我可以理解为什么它不允许我更改字段 但我能做些什么才能使其工作 我无法使用defaultValue因为我会将这些字段更改为其他
  • 使用 JavaScript 的计时器

    我想使用java脚本实现计时器 我想随着间隔的变化而减少计时器 Example假设我的计时器从 500 开始 我想要根据级别减少计时器 例如1 一级定时器应减1 且递减速度应较慢 2 2级定时器应递减2 递减速度应为中等3 3级定时器应减3
  • Window 与 Var 声明变量[重复]

    这个问题在这里已经有答案了 可能的重复 JavaScript 中使用 var 和不使用 var 的区别 https stackoverflow com questions 1470488 difference between using v
  • 使用 Lodash 的 TypeScript:_.map(["123", " 234 "], _.trim) 返回 boolean[]?

    我有一个字符串数组 它们已像这样分割 var searchValue 600 800 123 180 var groups searchValue split gt 600 800 123 180 因此项目周围可能存在空格 并且我想删除空格
  • 常规 JavaScript 可以与 jQuery 混合使用吗?

    例如 我可以采用这个脚本 来自 Mozilla 教程 https developer mozilla org en Canvas tutorial Basic usage
  • 如何在 JavaScript 中获取浮点数的小数位?

    我想要的是与 Number prototype toPrecision 几乎相反的 这意味着当我有数字时 它有多少位小数 例如 12 3456 getDecimals 4 对于任何想知道如何更快地完成此操作 无需转换为字符串 的人 这里有一
  • Serviceworker Bug event.respondWith

    我的 serviceworker 的逻辑是 当发生获取事件时 它首先获取包含一些布尔值 而不是 event request url 的端点 并根据我正在调用的值检查该值event respondWith 对于当前的获取事件 我正在提供来自缓

随机推荐

  • 项目报错: Could not set property 'ID' of 'class cn.xxx.entity.vo.CrossVo' with value '1'

    今天写mybatis的时候遇到一个问题轮饶了大半天 后来才发现自己犯了非常幼稚的错误 今天写了一个很简单的表 然后利用Mybatis的反向工具生成实体和BaseResultMap里面的字段 后来查询的时候 一直报错 Could not se
  • 48-C语言-输出十个数,并判断最大值和计算平均值

    问题 输入十个数 计算最大值和平均值 思路 输入十个数 在数组内输入 所以定义一个数组先 a 10 再进行输入 再for循环里 进行输入 scanf d a i 之后定义最大值和平均值 由于平均值需要先算出总和 所以再定义一个总和的变量 之
  • 【自学Flutter】9 Flex布局和 Expanded 的使用

    9 Flex布局和 Expanded 的使用 1 源代码 import package flutter material dart void main gt runApp MyApp class MyApp extends Stateful
  • saltstack常用操作----salt-api 实现 通过http请求远程调用saltstack的步骤

    在master主机上面执行如下操作 1 创建登录的用户名和密码 useradd username pwsswd username 2 修改master的配置文件 进入下面的目录 cd etc salt maaster d touch eau
  • Firefox OS

    参考1 http www firefoxos cc thread 348 1 1 html 1月7日 Mozilla中国去的工程师在深圳腾讯大厦举办了Firefox新版本体验活动 其中一个话题是关于Mozilla Boot to Gecko
  • error LNK1104: 无法打开文件“****.lib”解决

    做Opencv项目时 在vs2010中导入opencv 出现了很多类似 error LNK1104 无法打开文件 lib 这样的错误 在网上找了一些方法说需要添加链接库 添加相应的lib库后还是不行 最后发现导入opencv的lib库是没有
  • python中使用gdal,osgeo

    目的 实现from osgeo import gdal 工具 win10 vc2015 gdal 2 2 2 download osgeo org gdal 2 2 2 用的13M的那个 我估计是64位的 就按64位操作了 py3 5 an
  • java cdn加速_webpack 引入 CDN 加速

    webpack 引入 cdn 加速 用比较流行的 vue webpack 来说明好了 1 在SPA模版入口页面 项目根目录下的 index html 中引入 CDNvue webpack cdn 2 在配置文件 build webpack
  • java.lang.NoclassDefFoundError:com/google/common/base/MoreObjects

    1 一次项目中编译出现错误 出现这种错误一般是因为我们的项目中集成了swagger 应为swagger是需要依赖于guava 所以会出现这种错误 2 在项目的pom 文件中添加guava依赖即可
  • hbase总结:如何监控region的性能

    转载 http ju outofmemory cn entry 50064 随着大数据表格应用的驱动 我们的HBase集群越来越大 然而由于机器 网络以及HBase内部的一些不确定性的bug 使得系统面临着一些不确定性的故障 因此 HBas
  • 腾讯笔试题:猜字游戏---猜1-100之间一个数字,最少多少次?第一次猜的数是几?

    题目 A B两人玩猜字游戏 游戏规则如下 A选定一个 1 100 之间的数字背对B写在纸上 然后让B开始猜 如果B猜的偏小 A会提示B这次猜的偏小 一旦B某次猜的偏大 A就不再提示 此次之后B猜的偏小A也不会再提示 只回答猜对与否 请问 B
  • 虚拟机vmare16使用u盘装安装ghost系统

    对于想要用vmware16U盘装系统的朋友来说 vmware16U盘装系统教程图解是非常重要的 但是如果手上没有vmware16U盘装系统操作步骤 怎么办好呢 注意以下文章为转载来的 所以vmware的版本是10为例的 本人亲测vm16也是
  • c++智能指针介绍之weak_ptr (&enable_shared_from_this)

    c 智能指针介绍之weak ptr c 11引入 头文件在
  • 使用navicat模型功能 快速理清表间关系

    模型 是一个用于创建和操作数据库模型的强大工具 在主窗口中 点击 模型 来打开模型的对象列表 注意 仅适用于 MySQL Oracle PostgreSQL SQLite SQL Server 和 MariaDB 一些主要的功能如下 创建和
  • CSS —— line-height详解

    本文导读 行高 指一行文子的高度 具体来说是指两行文子间基线间的距离 在CSS line height被用来控制行与行之间的垂直距离 line height 属性会影响行框的布局 在应用到一个块级元素时 它定义了该元素中基线之间的最小距离而
  • c语言蜂鸣声音音乐代码

    include include
  • 单例(Singleton)设计模式

    单例 Singleton 设计模式 1 概念 某个类只能存在一个对象实例 并且该类只提供一个取得该对象实例的方法 2 实现方式 懒汉式 这种写法线程不安全 package com lmwei p20 import com sun org a
  • SQL 高级查询

    select from score select from Student 统计函数 count总数 max最大 min最小 avg平均 sum求和 count select count from Student max select ma
  • 公有链VS联盟链

    相信有人也听说过区块链分为公有链 私有链和联盟链 那么 它们三者有何区别 业内外人士对于该采用哪种类型的区块链看法不一 并引发了诸多围绕公有链 联盟链与私有链的争论 但是业内普遍认为联盟链与私有链实属一类 目前金融机构多偏向私有链 但也有人
  • 详解vue中diff算法

    详解vue中diff算法 vue中diff算法 作用 1 真实DOM和虚拟DOM 2 diff的比较方式 diff涉及到的函数 总结 vue中diff算法 本质 找出两个对象之间的差异 核心 子节点数组进行比较 首尾两端对比 作用 渲染真实