【Vue源码解析】Vue虚拟dom和diff算法

news2024/11/29 6:29:15

Vue虚拟dom和diff算法

        • 1. 简介
        • 2. 搭建环境
            • 1. 安装snabbdom
            • 2. 安装webpack5并配置
        • 3、函数
            • 3.1 虚拟节点vnode的属性
            • 3.2 使用h函数 创建虚拟节点
            • 3.3 使用patch函数 将虚拟节点上DOM树
            • 3.4 h函数嵌套使用,得到虚拟DOM树(重要)
            • 3.5 patchVnode函数
            • 3.6 updateChildren
        • 4. 手写diff
            • 4.2 Node.appendChild()
            • 4.3 Node.removeChild
            • 4.4 document.createElement

🎞️🎞️🎞️ 博主主页: 糖 -O-


👉👉👉 react专栏:vue源码解析


🌹🌹🌹希望各位博主多多支持!!!

1. 简介

关系

diff是发生在虚拟DOM上的新虚拟DOM和旧虚拟DOM进行diff(精细化比较),算出应该如何最小量更新,最后反映到真正的DOM上

diff算法

  • diff算法是虚拟DOM技术的产物,vue里面实际叫做patch,它的核心实现来自于snabbdom;通过新旧虚拟DOM作对比(即patch),将变化的地方转换为DOM操作

  • 组件中数据发生变化时,对应的watcher会通知更新并执行其更新函数,它会执行渲染函数获取全新虚拟dom:newVnode,此时就会执行patch比对上次渲染结果oldVnode和新的渲染结果newVnode。

  • patch过程遵循深度优先、同层比较的策略;两个节点之间比较时,如果它们拥有子节点,会先比较子节点;比较两组子节点时,会假设头尾节点可能相同先做尝试,没有找到相同节点后才按照通用方式遍历查找;查找结束再按情况处理剩下的节点;借助key通常可以非常精确找到相同节点,因此整个patch过程非常高效。

✨✨✨ key的作用: key的作用主要是为了更高效的更新虚拟DOM。

从源码中可以知道,vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。

2. 搭建环境

1. 安装snabbdom
npm install -S snabbdom
2. 安装webpack5并配置
npm i -D webpack@5 webpack-cli@3 webpack-dev-server@3
//  配置webpack
module.exports = {
  // webpack5 不用配置mode
  // 入口
  entry: "./src/index.js",
  // 出口
  output: {
    // 虚拟打包路径,文件夹不会真正生成,而是在8080端口虚拟生成
    publicPath: "xuni",
    // 打包出来的文件名
    filename: "bundle.js",
  },
  // 配置webpack-dev-server
  devServer: {
    // 静态根目录
    contentBase: 'www',
    // 端口号
    port: 8080,
  },
};

3、函数

3.1 虚拟节点vnode的属性
	 {
		children: undefined// 子元素 数组
		data: {} // 属性、样式、key
		elm: undefined // 对应的真正的dom节点(对象),undefined表示节点还没有上dom树
		key: // 唯一标识
		sel: "" // 选择器
		text: "" // 文本内容
	}
3.2 使用h函数 创建虚拟节点
// 创建虚拟节点
var myVnode1 = h('a', { props: { href: 'https://www.baidu.com' } }, 'YK菌')
console.log(myVnode1)
3.3 使用patch函数 将虚拟节点上DOM树
// 创建patch函数
const patch = init([
  classModule,
  propsModule,
  styleModule,
  eventListenersModule,
]);

// 创建虚拟节点
var myVnode1 = h(
  "a",
  { props: { href: "https://www.baidu.com", target: "_blank" } },
  "YK菌"
);

// 让虚拟节点上树
let container = document.getElementById("container");
patch(container, myVnode1);
3.4 h函数嵌套使用,得到虚拟DOM树(重要)
const myVnode3 = h('ul', [
  h('li', '苹果'),
  h('li', '香蕉'),
  h('li', '西瓜'),
  h('li', '番茄'),
])

// 让虚拟节点上树
let container = document.getElementById("container");
patch(container, myVnode3);

将传入的参数组合成对象返回

  • 产生虚拟节点

  • 将传入的参数组合成对象返回

  • @param {string} sel 选择器

  • @param {object} data 属性、样式

  • @param {Array} children 子元素

  • @param {string|number} text 文本内容

  • @param {object} elm 对应的真正的dom节点(对象),undefined表示节点还没有上dom树

  • @returns

3.5 patchVnode函数

👉👉👉patchVnode
是用于比较两个相同节点的子级(文本,或子节点)的一个函数。故它的调用总是在sameVnode判断之后。只有判断当前比较的两个vnode相同时(这里我最后再解释一次,两个vnode相同仅仅代表key相同且sel选择器相同),才会被执行。

作用:对比新旧两个节点,更新dom的子级(子级包含文本或者是子节点)

1、如果新vnode有text属性

旧vnode是否有子节点,如果有,代表原来是子节点,现在变成文本了,那么删除子节点,并且设置vnode对象的真实dom的text值(使用setTextContent函数)
其他情况不用管,直接设置vnode对象的真实dom的text值

2、如果新vnode没有text属性

  • 如果新vnode和旧vnode都存在子节点时。是不是要深度对比两个vnode的子节点啊。这个时候会进入第三步,比较子节点(执行updateChildren)

  • 如果只有新vnode有子节点,老vnode没有,那么很简单,执行添加节点的操作

  • 如果只有旧vnode有子节点,新vnode没有子节点,很明显,要执行删除旧vnode子节点的操作

  • 如果两个vnode上都没有子节点。但旧节点有text,那么很简单,说明原来有文本,现在没有了,清空vnode对应dom的text

3.6 updateChildren

作用:用于比较新旧两个vnode的子节点

声明:下文中所指的匹配上,指的就是判断是否是sameVnode,即上文中所说的,key相同,sel选择器相同

4. 手写diff

首次上DOM树patch(container, myVnode1)

4.1

 var insertedNode = parentNode.insertBefore(newNode, referenceNode);

insertedNode :被插入节点(newNode)
parentNode :新插入节点的父节点
newNode:用于插入的节点
referenceNode :newNode 将要插在这个节点之前 在当前节点下增加一个子节点 Node,并使该子节点位于参考节点的前面。

4.2 Node.appendChild()

将一个节点附加到指定父节点的子节点列表的末尾处。
如果将被插入的节点已经存在于当前文档的文档树中,那么 appendChild() 只会将它从原先的位置移动到新的位置(不需要事先移除要移动的节点)。

4.3 Node.removeChild
DOM中删除一个子节点。返回删除的节点

let oldChild = node.removeChild(child);

//OR

element.removeChild(child);
  • child 是要移除的那个子节点.

  • node 是child的父节点.

  • oldChild保存对删除的子节点的引用. oldChild ===
    child.

  • child 是要移除的那个子节点.

  • node 是child的父节点.

  • oldChild保存对删除的子节点的引用.

  • oldChild ===
    child.

4.4 document.createElement
var element = document.createElement(tagName[, options]);

tagName:指定要创建元素类型的字符串, 创建元素时的 nodeName 使用 tagName 的值为初始化,该方法不允许使用限定名称(如:“html:a”),在 HTML 文档上调用 createElement() 方法创建元素之前会将tagName 转化成小写,在 Firefox、Opera 和 Chrome 内核中,createElement(null) 等同于 createElement(“null”)

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/363949.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mac 上搭建 iOS WebDriverAgent 环境

文章目录Mac环境搭建配置 Xcode 生成 WDA常见问题brew 安装失败Mac环境搭建 macOS 系统电脑:12.6.2 Xcode:14.0.1(xcodebuild -version) appium Desktop:1.21.0 (下载链接) Appium Desktop 1.22.0 ,从该版…

Redis 高级数据类型

文章目录一、Bitmaps:属性状态统计二、HyperLogLog:基数统计三、GEO:地理位置信息计算提示:以下是本篇文章正文内容,Redis系列学习将会持续更新 一、Bitmaps:属性状态统计 Bitmaps类型: 统计一…

数据结构:循环队列的实现(leetcode622.设计循环队列)

目录 一.循环队列简单介绍 二.用静态数组实现循环队列 1.数组循环队列结构设计 2.数组循环队列的堆区内存申请接口 3.数据出队和入队的接口实现 4.其他操作接口 5.数组循环队列的实现代码总览 三.静态单向循环链表实现循环队列 1.链表循环队列的结构设计 2.创建静…

Nginx第二讲

目录 二、Nginx02 2.1 keepalived和heartbeat介绍 2.1.1 两者的介绍 2.1.2 keepalived简介 2.1.3 VRRP协议与工作原理 2.1.4 Keepalvied的工作原理 2.2 安装环境及keepalived 2.3 启动与验证keepalived 2.4 keepalived测试 2.4.1 环境准备 2.4.2 配置keepalived 2.…

守护最后一道防线:Coremail邮件安全网关推出邮件召回功能

根据Coremail邮件安全大数据中心2022年Q4季报显示,2021年CAC识别钓鱼邮件1.81亿,2022年上升至2.25亿,增幅高达24.1%。 这表明2022年平均每天有61万7088封钓鱼邮件被接收及发出,企业用户面临潜在经济损失不可估量。 尤其是活跃至今…

Linux中安装JDK

Linux中安装JDK一 、下载JDK包1、下载网址2、往下翻,找到 java83、继续往下翻找到要下载的版本 64位linux版本二 上传jdk安装包三 开始安装整体过程1、解压文件2、查看解压文件3、进入解压文件夹确认4、配置环境变量5、重新加载环境变量6、确认安装成功一 、下载JDK…

Leetcode11. 盛最多水的容器

一、题目描述: 给定一个长度为 nnn 的整数数组 heightheightheight 。有 nnn 条垂线,第 iii 条线的两个端点是 (i,0)(i, 0)(i,0) 和 (i,height[i])(i, height[i])(i,height[i]) 。 找出其中的两条线,使得它们与 xxx 轴共同构成的容器可以容…

【C++】类与对象(引入)

目录 前言 类的引入 类的定义 封装与访问限定符 封装 访问限定符 类的实例化 类的大小 this指针 特性 前言 🎶我们都知道,C语言是面向过程的编程,而C是面向对象的编程,更多体现在编程的关注点上。 🎶就拿洗…

ES6--class类(详解/看完必会)

目录 1、基本概念 2、基本用法 3、class与构造函数的区别 4、constructor的使用 5、自定义方法 6、extends和super (1)问题一:我们想要在点击按钮二的时候改变字体大小,如何写呢? (2)问…

【极海APM32替代笔记】低功耗模式配置及配置汇总

【极海APM32替代笔记】低功耗模式配置及配置汇总 文章总结:(后续更新以相关文章为准) 【STM32笔记】低功耗模式、WFI命令等进入不了休眠的可能原因(系统定时器SysTick一直产生中断) 【STM32笔记】HAL库低功耗模式配置…

万字长文解析!复现和使用GPT-3/ChatGPT,你所应该知道的

关于作者 英文原版作者:杨靖锋,现任亚马逊科学家,本科毕业于北大,硕士毕业于佐治亚理工学院,师从 Stanford 杨笛一教授。 杨昊桐 译,王骁 修订 感谢靳弘业对第一版稿件的建议,感谢陈三星&am…

timm使用swin-transformer

1.安装 pip install timm2.timm中有多少个预训练模型 #timm中有多少个预训练模型 model_pretrain_list timm.list_models(pretrainedTrue) print(len(model_pretrain_list), model_pretrain_list[:3])3加载swin模型一般准会出错 model_ft timm.create_model(swin_base_pat…

Head First设计模式---4.工厂方法模式

2.1工厂方法模式 亦称: 虚拟构造函数、Virtual Constructor、Factory Method 工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。 [外链图片转存失败,源站可能有防盗链机制,建议将图片…

Linux 终端复用器Tmux

目录 Tmux讲解 配置tmux 配置tmux会话 配置tmux窗口(在会话界面进行配置) 配置tmux面板 配置窗口共享同步 Tmux讲解 RHEL5/6/7使用的是screen软件包 RHEL8使用的是tumx软件包(功能更强大,更易用) tmux的三个基本…

阿里云物联网平台设备模拟器

在使用阿里云物联网平台过程中,如果开始调试没有实际的物理设备,可以考虑在阿里云物联网平台使用官方自带的模拟器进行调试。不过也可以通过叶帆科技开发的阿里云物联网平台设备模拟器AliIoTSimulator进行调试,AliIoTSimulator可以独立运行&a…

第49章 API统一集中管理

1 关于统一集中管理API的一些思考 1、统一集中管理是保证工程性项目得保质、保量、成功实施,并对后期维护提供数据支撑的最有效,最节省资源和时间的技能和做法,软件做为一种特殊的工程性项目,也符合上述特性。 2、由于在前台实现中…

Leetcode6. N字形变换

一、题目描述: 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下: 之后,你的输出需要从左往右逐行读取,产…

【数据结构与算法】算法的时间复杂度和空间复杂度

文章目录前言1.算法效率1.1.如何衡量一个算法的好坏1.2.算法的复杂度2.时间复杂度2.1.时间复杂度的概念2.2.大O的渐进表示法2.3.常见时间复杂度计算举例2.4.常见时间复杂度3.空间复杂度4.复杂度oj练习Practice.1 消失的数字Practice.2 旋转数组写在最后前言 关于时空复杂度的分…

到底什么样的条件才能被浙大MBA录取?攻略集合

新一年管理类联考已悄然启动,很多考生把目标也都放在了浙江大学MBA项目上,那么浙江大学MBA项目好考吗?报考流程是怎样的?杭州达立易考教育在这里给大家汇总整理了浙大MBA项目相关资讯,分享给想要报考浙大MBA的同学&…

每天一道大厂SQL题【Day12】微众银行真题实战(二)

每天一道大厂SQL题【Day12】微众银行真题实战(二) 大家好,我是Maynor。相信大家和我一样,都有一个大厂梦,作为一名资深大数据选手,深知SQL重要性,接下来我准备用100天时间,基于大数据岗面试中的经典SQL题&…