知食记
  • 知食记
  • 思维导图
  • 归档
  • 博客
    • 理解 CAP 理论,以开一家餐厅为例
    • TypeScript tsconfig.json 整理
    • this 陷阱与原理
    • 时间戳与时区
    • vue-cli 项目添加 tailwind.css
    • 实现 JavaScript 数组的逆序索引
    • 工程师段位记
    • Jest 内部原理剖析
    • 如何成为技术高手
    • Vue lifecycle hook little trick
    • 利用 Cloudflare Worker 定制 Gitbook
    • 如何解除端口占用?
  • 🎃CSS
    • CSS基础
      • 盒模型
      • 清除浮动
      • html5的语义化标签
      • 水平居中
      • 垂直局中
      • 水平垂直居中
      • Sticky Footer
      • 三栏布局
      • 文本截断省略号
      • 伪类与伪元素
      • 定位
      • BEM
      • 主题切换
      • 权重
      • px, em, rem
      • flex 布局
    • CSS3
      • calc
    • SCSS
      • lighten
  • 🎉JavaScript
    • JS 概念
      • 类型
      • 类型转换
      • 内存
      • 原型链
      • 继承
        • 类式继承+原型继承
        • 构造函数继承
        • 组合式继承
        • 寄生继承
        • 寄生组合
      • 词法作用域
      • 事件委托
      • Falsy
      • This
      • 闭包
      • Event-loop
      • 跨域
      • function.length
      • arguments
      • !!
      • void 0
      • 柯里化1
      • 柯里化2
      • 异常
      • 协程
    • JS陷阱
      • 相等
      • 连等赋值
      • 改变数组的length
      • 引用传参
      • new Number vs Number
      • new Object vs Object vs Object.create(null)
    • JS开发知识点
      • html classlist
      • 图片懒加载
      • 提取对象中所有属性除了某一项
      • 空值判断
    • 实现JS常见函数
      • Debounce
      • Throttle
      • Call, Apply, Bind
      • type
      • 深拷贝
      • isEuqal
      • 数组乱序
      • 数组去重
      • 实现 merge
      • 数组flat
      • 实现 map
      • 数组filter
      • 模拟new
      • 模拟实现async
      • 模拟instance of
      • Object.create(null)与{}
      • 实现promisify
      • 实现Promise.all
      • 实现Promise.race
      • 实现Promise.resolve/reject
      • 实现Promise.finnaly
      • 实现Promise
      • 实现parseInt
      • 实现foreach
      • 实现Object keys
    • 实现JS 常见操作函数
      • JS Empty
      • JS Safe Get
    • JS Worker
      • Cloudflare Worker
    • ES6
      • ES6/ES7/ES8/ES9索引
      • +,-,** 运算符
      • Let 知识点
      • 块级作用域
      • Const知识点
      • Class
      • Proxy
      • Reflect
      • Symbol
      • Promise
      • Iterator
      • For-of
      • for..in 与 for..of的区别
      • Iterator
      • Generator函数
      • async
      • 装饰器
      • 模块
      • WeakMap
      • 模块
      • 尾调用优化
    • ES6 函数
      • concat
      • reduce
      • slice
      • splice
      • Array.some
      • Array every
      • Array.prototype.includes
      • Object.entries/Object.values/Object.keys
    • Typescript
      • 使用
        • 基础类型
        • ?: 可选属性
        • Keyof
        • is
        • in
        • Partial
        • DeepPartial
        • Required
        • Exclude
        • Pick
        • Omit
        • infer
        • ReturnType
        • Record
        • 重载
        • 泛型变量
        • 泛型接口
        • 字面量类型守卫
        • type和interface的区别
        • 赋值断言
        • 类型断言
      • 编译原理
        • 编译器
      • 设计工具类型(重要)
    • V8
      • JS 数组
      • 垃圾回收
  • 🕹️框架
    • Vue
      • 基础知识
      • 长列表性能优化
      • mixin
      • 渲染函数
      • 组件间通信
      • vue中的柯里化闭包
      • vue 渲染过程
      • Vue采用虚拟DOM的目的是什么
      • keep-alive
      • nextTick
      • vue 数组变异
      • vue-computed原理
      • vue-router原理
      • vue-router权限控制
      • 路由懒加载
      • Vue diff原理
      • computed如何与视图绑定
      • scope css
      • Runtime Only vs Runtime + Compiler
      • 集中变量管理
        • 程序化的事件侦听器
        • Flux
        • Redux
        • Vuex
    • Vue3
      • Object.defineProperty与Proxy区别
    • React
      • 无法preventDefault
      • Parent控制Child(Lifting state up)
      • Dynamic Ref
      • useRef warning
      • 定义固定长度的数组
    • React-Redux
      • 基础概念
      • mapStateToProps
      • mapDispatchToProps
      • Provider
    • React Hooks
      • useState
      • useEffect
      • useContext
      • useReducer
      • useMemo
    • Nuxt
      • SSR 与 预渲染
    • Koa2
      • compose
  • 🎯算法
    • 算法与数据结构
      • 基础知识
        • 大O表示法
      • 排序
        • 基础知识
        • 冒泡排序 bubble sort
        • 选择排序
        • 插入排序
        • 前三种总结
        • 归并排序
        • 快速排序
        • 以上总结
      • 递归
        • 实现斐波那契数列
        • 深拷贝
        • Array.flat 实现
        • 爬楼梯
        • 递归问题
      • 队列
        • 队列模仿栈
      • 二叉树专题
        • 基本结构
        • 前序遍历
        • 中序遍历
        • 后序遍历
        • 重建二叉树
        • 求二叉树的遍历
        • 相同的树
      • 回溯法
      • JS 大数相加
      • 动态规划
        • 爬楼梯
      • 二分搜索
      • LRU
      • 数据结构
        • 数组
        • 栈
        • 队列
        • 链表
          • 单链表
          • 双向链表
          • 循环列表
        • 集合
        • 字典
      • Leetcode
        • 1. 两数之和
        • 2. 两数相加
        • 3. 无重复字符最长字串
        • 5. 最长回文子串
        • 6. Z字形变换
        • 7. 整数反转
        • 8. 字符串转换整数
        • 15. 三数之和
        • 134. 加油站
  • 🎁HTML
    • DOM
      • MutationObserver
      • node.contains
      • readystate
    • SVG
      • 坐标系
      • g元素
      • 实现缩放
      • react typescript svg相关
  • 🏈计算机网络
    • 浏览器
      • 浏览器与JS 线程
      • 缓存
      • 浏览器的本地存储
      • 输入URL到页面加载发生了什么
      • Preload, Prefetech,DNS Prefetching
      • defer, async 脚本
      • 前端监控异常捕捉
      • 内存泄露
      • 指纹
      • XSS
      • CSRF
      • 性能优化
      • *网页优化
      • requestAnimationFrame
      • 问题清单
        • 为什么 Javascript 是单线程的?
    • 计算机网络
      • 基础知识
      • 代理
      • HTTP1/2/3
      • HTTPS
      • HTTP请求中的keep-alive
      • http的方法
      • HTTP状态码
      • 运输层
        • TCP
          • 可靠传输
        • UDP
      • CA
      • CORS
      • Referer
      • referer, host, origin对比
      • websocket
      • CDN
  • 🥊前端生态
    • Webpack
      • 基础概念
      • 配置记录
      • sourcemap
      • 常用Webpack插件记录
      • webpack相关竞品
      • HMR
      • Tree-shaking的原理
      • Long Term Cache
      • Code Splitting
    • Babel
    • Fetch
      • Fetch取消请求
    • Axios
      • axios避免重复请求
      • 运行机制
      • 取消源码
      • axios做到多种调用方式
    • Npm
      • 设置镜像源
      • 全局安装目录
      • npx
      • 镜像源
    • Yarn
      • yarn link
    • 业务开发
      • 记住登陆
      • 统一登录/单点登录/SSO
      • 大文件上传与断点续传
      • 防止表单重复提交
      • 扫码登录如何实现的
      • 高性能网站标准
      • 性能监控
        • performance 分析
        • 上报
        • 首屏时间
    • 微前端
      • 微前端实施方式
      • single-spa重要概念
    • Hexo
      • 常用Hexo插件记录
  • 🏀后端
    • Node
      • nodejs的应用
      • API 网关
    • Java
      • 简介
    • Python
      • Pyenv
  • 🕹️面试
    • 面试真经
      • Set、Map、WeakSet 和 WeakMap 的区别?
      • Map+ParseInt
      • 红绿灯Promise问题
      • 0.1 + 0.2
      • vue面试清单
      • 状态码问题 301 302
      • 算法和数据结构清单
      • 腾讯面经
        • 电话面试
          • window的onload事件和domcontentloaded顺序
        • 远程面
          • new和instanceof的内部机制
          • typeof 和 instanceof 区别
          • flex-grow和flex-shrink属性有什么用?
      • 头条面经
        • 笔试
          • 153812.7 转化153,812.7
          • 日期转化为2小时前,1分钟前等
          • 实现sum(1)(2)(3) = 6
          • 最多频次
          • 类继承面试
          • 前端请求并发控制
          • CSS画三角形
          • CSS 画正方形
          • 下载页面的所有图片
          • 实现链式调用
          • 100 * 100 的 Canvas 占内存多大
      • javascript-questions
        • 原型链与new优先级
        • 函数+模板字符串
        • 对象引用
        • use strict
        • 声明提升相关
        • 对象键值存储
        • target与事件冒泡
        • call与bind
        • falsy
        • map, reduce和正则
        • event-loop与promise
        • 变量提升和声明作用域
    • To-do
      • axios 重放多种策略
  • 🤖开源
    • 开源项目
      • Hooks
      • 论坛
      • 开发流程
      • 前端监控
      • 模板
      • svg
  • 🧸其他
    • Linux
      • 免密登陆
      • Mac终端代理
    • Git
      • 创建 ssh rsa
      • 删除分支
      • 覆盖已有提交
      • 合并多个commit
      • 撤销merge
    • 正则
      • 题目
        • 转换为驼峰命名法
        • JS实现千位分隔符
        • 获取 url 参数
        • 用正则实现trim() 清除字符串两端空格
    • 设计模式
      • 简单工厂模式
      • 抽象工厂模式
      • 单例模式
      • 装饰器模式
      • 适配器模式
      • 代理模式
      • 观察者模式
      • 发布-订阅模式
      • 观察者模式与发布-订阅模式的区别是什么?
      • 单例模式
      • 工厂模式
      • 享元模式
    • 计算机理论
      • solid原则
        • SRP | The Single Responsibility Principle
        • OCP | The Open Closed Principle
        • LSP | The Liskov Substitution Principle
        • ISP | The interface Segregation Principle
        • DIP | The Dependency Inversion Principle
由 GitBook 提供支持
在本页
  • 灵感来源
  • Vitrual DOM与真实DOM
  • 详解
  • 仅在同层比较,不会跨层级比较
  • 流程图
  • 代码部分
  • patch
  • patchVnode
  • updateChildern(核心)

这有帮助吗?

  1. 🕹️框架
  2. Vue

Vue diff原理

上一页路由懒加载下一页computed如何与视图绑定

最后更新于5年前

这有帮助吗?

灵感来源

Vitrual DOM与真实DOM

真实DOM

<div>
    <p>123</p>
</div>

Virtual DOM(伪代码)

var Vnode = {
  tag: "div",
  children: [{ tag: "p", text: "123" }]
};

详解

仅在同层比较,不会跨层级比较

流程图

代码部分

patch

function patch(oldVnode, vnode) {
  // some code
  if (sameVnode(oldVnode, vnode)) {
    patchVnode(oldVnode, vnode);
  } else {
    const oEl = oldVnode.el; // 当前oldVnode对应的真实元素节点
    let parentEle = api.parentNode(oEl); // 父元素
    createEle(vnode); // 根据Vnode生成新元素
    if (parentEle !== null) {
      api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)); // 将新元素添加进父元素
      api.removeChild(parentEle, oldVnode.el); // 移除以前的旧元素节点
      oldVnode = null;
    }
  }
  // some code
  return vnode;
}

patch函数主要进行了两个分支流程

  1. 判断两节点是否是一样的,如果是则执行 patchVnode

  2. 不一样则用 Vnode 替换 oldVnode

patchVnode

patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        api.setTextContent(el, vnode.text)
    }else {
        updateEle(el, vnode, oldVnode)
    	if (oldCh && ch && oldCh !== ch) {
            updateChildren(el, oldCh, ch)
    	}else if (ch){
            createEle(vnode) //create el's children dom
    	}else if (oldCh){
            api.removeChildren(el)
    	}
    }
}

关键流程

  1. 找到对应的真实 dom,称为 el

  2. 判断 Vnode 和 oldVnode 是否指向同一个对象,如果是,那么直接 return

  3. 如果他们都有文本节点并且不相等,那么将 el 的文本节点设置为 Vnode 的文本节点

  4. 如果 oldVnode 有子节点而 Vnode 没有,则删除 el 的子节点

  5. 如果 oldVnode 没有子节点而 Vnode 有,则将 Vnode 的子节点真实化之后添加到 el

  6. 如果两者都有子节点,则执行 updateChildren 函数比较子节点,这一步很重要

updateChildern(核心)

我们将它们取出来并分别用 s 和 e 指针指向它们的头 child 和尾 child

现在分别对 oldS、oldE、S、E 两两做 sameVnode 比较,有四种比较方式,当其中两个能匹配上那么真实 dom 中的相应节点会移到 Vnode 相应的位置,这句话有点绕,打个比方

  • 如果是 oldS 和 E 匹配上了,那么真实 dom 中的第一个节点会移到最后

  • 如果是 oldE 和 S 匹配上了,那么真实 dom 中的最后一个节点会移到最前,匹配上的两个指针向中间移动

  • 如果四种匹配没有一对是成功的,分为两种情况

    • 如果新旧子节点都存在 key,那么会根据 oldChild 的 key 生成一张 hash 表,用 S 的 key 与 hash 表做匹配,匹配成功就判断 S 和匹配节点是否为 sameNode,如果是,就在真实 dom 中将成功的节点移到最前面,否则,将 S 生成对应的节点插入到 dom 中对应的 oldS 位置,oldS 和 S 指针向中间移动。

    • 如果没有 key,则直接将 S 生成新的节点插入真实 DOM(ps:这下可以解释为什么 v-for 的时候需要设置 key 了,如果没有 key 那么就只会做四种匹配,就算指针中间有可复用的节点都不能被复用了)

再配个图(假设下图中的所有节点都是有 key 的,且 key 为自身的值)

  • 1.第一步

oldS = a, oldE = d;S = a, E = b;

oldS 和 S 匹配,则将 dom 中的 a 节点放到第一个,已经是第一个了就不管了,此时 dom 的位置为:a b d

  • 2. 第二步

oldS = b, oldE = d;S = c, E = b

oldS 和 E 匹配,就将原本的 b 节点移动到最后,因为 E 是最后一个节点,他们位置要一致,这就是上面说的:当其中两个能匹配上那么真实 dom 中的相应节点会移到 Vnode 相应的位置,此时 dom 的位置为:a d b

  • 3. 第三步

oldE 和 E 匹配,位置不变此时 dom 的位置为:a d b

  • 4. 第四步

遍历结束,说明 oldCh 先遍历完。就将剩余的 vCh 节点根据自己的的 index 插入到真实 dom 中去,此时 dom 位置为:a c d b 这个匹配过程的结束有两个条件:

  • oldS > oldE 表示 oldCh 先遍历完,那么就将多余的 vCh 根据 index 添加到 dom 中去(如上图)

  • S > E 表示 vCh 先遍历完,那么就在真实 dom 中将区间为[oldS, oldE]的多余节点删掉

GitHub - snabbdom/snabbdom: A virtual DOM library with focus on simplicity, modularity, powerful features and performance.GitHub
Logo
粉红色的部分为 oldCh | 黄色部分为 vCh