Skip to content
On this page

vue 脚手架中 assset 和 public 有什么区别

  • public 目录下的文件会被 url-loader 处理,并且 url-loader 默认会将小于 10kb 的文件转成 base64 格式。
  • asset 目录下的文件会直接被复制,并且保留原始文件的扩展名。

因此,如果需要引入一个文件,并且该文件小于 10kb,那么推荐将该文件放在 public 目录下,否则推荐将该文件放在 asset 目录下。

说一下 vue 生命周期

Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。

  • beforeCreate:(创建前)数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不 能访问到 data、computed、watch、methods 上的方法和数据。

  • created:(创建后)实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的计算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

  • beforeMount:(挂载前)在挂载开始之前被调用:相关的 render 函数首次被调用。

  • mounted:(挂载后)在 el 被新创建的 vm.$el 替换,并挂载到实 例上去之后调用。实例已完成以下的配置:用上面编译好的 html 内 容替换 el 属性指向的 DOM 对象。完成模板中的 html 渲染到 html 页 面中。

  • beforeUpdate:(更新前)数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。

  • updated:(更新后)在由于数据更改导致的虚拟 DOM 重新渲染和 打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM 已经更新,所以可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更 新无限循环。该钩子在服务器端渲染期间不被调用。

  • beforeDestroy:(销毁前)实例销毁之前调用。在这一步,实例仍然完全可用。

  • destroyed:(销毁后)Vue 实例销毁后调用。该钩子被调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

  • activated:(激活)keep-alive 组件激活时调用。

  • deactivated:(停用)keep-alive 组件停用时调用。

v-if 和 v-for 哪个优先级更高?如果同时出现,应该怎么优化以提高性能?

v-for 优先于 v-if 被解析,如果同时出现,每次渲染都会先执行循 环再判断条件,无论如何循环都不可避免,浪费了性能。

要避免出现这种情况,则在外层嵌套 template,在这一层进行 v-if 判断,然后在内部进行 v-for 循环。如果条件出现在循环内部,可通 过计算属性提前过滤掉那些不需要显示的项。

vue 子组件和父组件加载顺序

加载渲染过程:

父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

子组件更新过程:

父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

父组件更新过程:

父 beforeUpdate -> 父 updated

销毁过程:

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

组件中的 data 为什么是一个函数?

每个组件都是被复用的,data 必须是函数,如果是对象的话,那么每次复用组件的时候,这个 data 都会是同一个对象,也就是说,一个组件的 data 会被复用,那么多个组件的 data 也会被复用,这样就会导致一个问题,比如两个组件都引用了同一个对象,其中一个组件修改了该对象上的属性,那么另一个组件也会受到影响。

vue 路由的 hash 和 history 模式的区别

简介: hash 模式是开发中默认的模式,它的 URL 带着一个#,例如:,它的 hash 值就是#/vue。

特点:hash 值会出现在 URL 里面,但是不会出现在 HTTP 请求中,对 后端完全没有影响。所以改变 hash 值,不会重新加载页面。这种模 式的浏览器支持度很好,低版本的 IE 浏览器也支持这种模式。hash 路由被称为是前端路由,已经成为 SPA(单页面应用)的标配。 原理: hash 模式的主要原理就是 onhashchange()事件:

window.onhashchange = function (event) {
  console.log(event.oldURL, event.newURL);
};

使用 onhashchange()事件的好处就是,在页面的 hash 值发生变化时,无需向后端发起请求,window 就可以监听事件的改变,并按规则加 载相应的代码。除此之外,hash 值变化对应的 URL 都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后 端服务器,但是页面的 hash 值和对应的 URL 关联起来了。

history 模式是 HTML5 新推出的功能,主要原理是,利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。也就是说,能向前或向后跳转页面,还能添加历史记录。只是,它们添加历史记录时不进行页面刷新)

URL 中没有#,它使用的是传统的路由分发模 式,即用户在输入一个 URL 时,服务器会接收这个请求,并解析这个 URL,然后做出相应的逻辑处理

特 点 :当 使 用 history 模 式 时 , URL 就 像 这 样 :。相比 hash 模式更加好看。但是,history 模式需要后台配置支持。如果后台没有正确配置,访问时会返回 404。

Vue3.0 有什么更新

  1. 监测机制的改变

3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的 反应性跟踪。

消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的 很多限制:

  • 只能监测属性,不能监测对象
  • 检测属性的新增和删除,需要使用 Vue.set 和 Vue.delete
  • 检测属性的双向绑定,需要使用 Vue.set
  • 检测数组下标和 length 属性,需要使用 Vue.set
  1. 模板的改变
  • 只能有一个根元素
  • 作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重 新渲染,提升了渲染的性能。

同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方 便习惯直接使用 api 来生成 vdom 。

  1. 对象式的组件声明方式
vue2.x 中 的 组 件 是 通 过 声 明 的 方 式 传 入 一 系 列 option , 和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功 能,但是比较麻烦。
3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易
  1. 其它方面的更改
  • 支持 TypeScript 4.0
  • 支持 Vue.js 2.x 的单文件组件
  • 支持自定义渲染器

defineProperty 和 proxy 的区别

Vue 在 实 例 初 始 化 时 遍 历 data 中 的 所 有 属 性 , 并 使 用 Object.defineProperty 把这些属性全部转为 getter/setter。这样 当追踪数据发生变化时,setter 会被自动调用。

Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就 是 Vue 不支持 IE8 以及更低版本浏览器的原因。

但是这样做有以下问题:

1.添加或删除对象的属性时,Vue 检测不到。因为添加或删除的对象 没 有 在 初 始 化 进 行 响 应 式 处 理 , 只 能 通 过 $set 来 调 用 Object.defineProperty()处理。

2.无法监控到数组下标和长度的变化。

Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚 举,函数调用等)。相对于 Object.defineProperty(),其有以下特 点:

1.Proxy 直接代理整个对象而非对象属性,这样只需做一层代理就可 以监听同级结构下的所有属性变化,包括新增属性和删除属性。2.Proxy 可以监听数组的变化。

Proxy 为什么要配合 Reflect 使用?

Reflect 是一个内置的全局对象,提供了拦截 JavaScript 操作的方法。Vue 3 使用 Reflect 来确保在代理对象上的操作与普通对象上的操作行为一致。这样,即使在代理对象上进行操作,也可以保持预期的行为,避免因为代理而引入的潜在问题。

如果不配合 Reflect 会有什么问题?

  • 如果不使用 Reflect,直接在 Proxy 的 handler 中处理各种操作,可能会导致一些不符合预期的行为。例如,Reflect 确保了方法调用时 this 的正确指向,如果没有 Reflect,可能需要手动处理 this 的绑定,增加了代码的复杂性。
  • 另外,Reflect 提供了一些与全局函数相对应的方法,如 Reflect.get、Reflect.set 等,这些方法在处理继承和访问器属性时比直接使用对应的全局函数更可靠。

Vue 3 中的 Proxy 有什么限制?

尽管 Proxy 提供了强大的功能,但它也有一些限制。例如,它不能代理一些内置的属性和方法,如 Object.prototype 上的属性。此外,Proxy 对象的性能可能略低于直接使用对象,尤其是在大量属性操作的场景下。

Vue 3 中的响应式系统如何处理数组?

Vue 3 使用 Proxy 来实现响应式对象,但对于数组,它使用了 Array 的 Proxy 版本,这样可以直接拦截数组索引的访问和赋值操作。Vue 3 还重写了数组的一些方法,如 push、pop、splice 等,以确保这些操作也是响应式的。

Vue 3 中的响应式系统如何优化性能?

Vue 3 通过批量异步更新、依赖收集和避免不必要的依赖追踪等技术来优化性能。此外,它还引入了 reactive、readonly 和 shallowReactive 等 API,允许开发者根据需要选择不同的响应式模式。

Vue 的响应式原理是什么?

Vue 的响应式系统的核心在于: 当数据发生改变时,视图会自动更新。 这套系统主要是通过“数据劫持”结合“发布-订阅”模式来实现的。需要注意的是,vue2 和 vue3 的响应式原理有所不同。

vue2 的响应式原理是使用 Object.defineProperty() 。

  1. 数据劫持与追踪变化
  • 在组件初始化时,Vue 会遍历 data 函数返回对象的所有属性
  • 对每一个属性, Vue 使用 Object.defineProperty() 方法进行数据劫持,为每个属性添加 getter 和 setter 方法
  • getter: 当模板或者计算属性访问该属性时,会触发 Getter。 此时 Vue 会将当前的渲染 Watcher 或计算属性 Watcher 收集为这个数据的“依赖”
  • setter: 当该属性被修改时,会触发 Setter。此时 Vue 会通知所有收集的依赖进行更新
  1. 依赖收集与 Watcher
  • Dep 类: 每一个被劫持的属性都会拥有一个唯一的 Dep 实例,它用来管理一个依赖列表(subs), 即所有用到这个数据的地方。
  • Watcher 类: Watcher 是一个中介角色。视图渲染、计算属性、侦听器都会创建一个 Watcher 实例,当 Watcher 被创建时。它会执行一次求值函数,在这个过程中会触发数据的 getter 方法, 从而将自身(this)添加到对应属性的 Dep 中。

简单的流程可以概况为:

组件渲染 -> 触发数据的 Getter -> Dep 收集当前 Watcher -> 数据变化 -> 触发 Setter -> Dep 通知所有的 Watcher -> Watcher 执行更新(如重新渲染)

  1. 数组响应式处理
  • Object.defineProperty() 无法监听数组索引的变化和长度的变化(如 arr[0] = x 或 arr.length = 0)。
  • 为了解决这个问题, Vue2 重新写了数组的 7 个变异方法:push、pop、shift、unshift、splice、sort、reverse
  • 当调用这些方法时,Vue 除了执行方法本身的功能外,还会通知依赖更新。因此在 Vue2 中, 直接通过索引设置数组项或者修改 length 是无法触发视图的更新的,必须使用 Vue.set() 或数组的变异方法。

Vue2 的缺陷

  1. 无法检测对象属性的添加或删除:必须使用 Vue.set() 或 Vue.delete() 方法才能触发视图更新。
  2. 无法检测数组索引的变化和长度的变化:必须使用数组的变异方法才能触发视图更新。
  3. 需要对每一个属性递归遍历,性能开销较大。

Vue3 的响应式原理是使用 Proxy 。

Vue 3 使用了更强大的 Proxy 来重构响应式系统,彻底解决了 Vue 2 的诸多限制。

  1. 更强大的数据劫持
  • Proxy 可以直接代理整个对象,而不是单个属性。它可以拦截多达 13 种操作,包括属性的读取、设置、删除、in 操作符等。
  • 拦截 get: 当访问对象的任何属性时,进行依赖收集
  • 拦截 set: 当设置或新增对象的任何属性时,触发更新。
  • 拦截 deleteProperty: 当删除对象属性时,触发更新。
  1. 实现细节
  • Reactive 函数:使用 Proxy 创建响应式对象.
  • Effect: 取代了 Vue2 中的 Watcher,它是一个副作用函数,当它内部依赖的响应性数据发生变化时,会重新执行该函数。Vue 的组件渲染和计算都是在一个 effect 中执行的。
  • Track: 在 Proxy 的 get 拦截器中调用,用于追踪当前运行的 effect 作为依赖。
  • Trigger: 在 Proxy 的 set 或者 deleteProperty 拦截器中调用,用于找到所有追踪了该属性的 effect 并执行它们。

简单流程概括为:

组件渲染(在 effect 中) -> 触发 Proxy 的 get -> track 收集当前 effect -> 数据变化 -> 触发 Proxy 的 set -> trigger 通知所有的 effect -> effect 重新执行(如重新渲染)

Vue 3 使用 Proxy 的优势:

全面的响应性: 可以检测属性的新增和删除,以及数组的索引和长度变化。

更好的性能: 无需在初始化时递归遍历所有属性,而是惰性劫持,只在属性被访问时才进行代理。

支持更多数据结构: 原生支持 Map, Set, WeakMap, WeakSet 等集合类型。

总而言之,Vue 的响应式系统通过精妙的依赖收集和触发机制,实现了数据与视图的自动同步。Vue 3 通过采用 Proxy,提供了更强大、更高效、更直观的响应式体验。

vue3 相对 vue2 当中做了哪一些优化

Vue 3 相对于 Vue 2 进行了一次全方位的重构,其优化点遍布于性能、代码组织、开发体验扩展性等多个层面。这些优化使得 Vue 3 应用运行更快、体积更小、更适合大型项目开发。

以下是我总结的几个核心优化方向:


1. 性能优化

这是 Vue 3 最显著的改进,直接提升了应用的运行速度和资源消耗。

  • 更小的打包体积

    • Tree-shaking: Vue 3 的绝大多数 API 都支持 Tree-shaking。这意味着如果你在项目中没有使用诸如 transitionv-model 等内置组件或功能,它们最终的打包体积将被移除。而在 Vue 2 中,无论你用不用,这些代码都会被完整打包。
    • 结果: 一个 Hello World 项目的打包体积可以减少到原来的一半(约 13kb → 约 6kb)。
  • 更快的渲染速度

    • 编译时优化: Vue 3 的编译器在编译模板时,会进行积极的静态分析和优化。
      • Patch Flag: 在创建 Virtual DOM 时,会对动态节点(即可能变化的节点)添加一个标记(Flag),例如 TEXTCLASSPROPS 等。在后续的 diff 算法中,只需要对比这些带有标记的动态节点,完全跳过静态节点,大大减少了比较范围。
      • Hoist Static: 将模板中的静态节点、静态节点树提升到渲染函数之外。这样在每次重新渲染时就不再重新创建这些 VNode,直接复用上一次的,节省了内存和计算开销。
      • Cache Handlers: 对事件处理函数进行缓存,避免不必要的更新。例如 @click="handleClick",如果 handleClick 没有变化,则直接使用缓存。
  • 响应式系统重写

    • 使用 Proxy 替代 Object.defineProperty,这带来了根本性的性能提升:
      • 初始化性能Proxy 是代理整个对象,无需像 Vue 2 那样递归遍历所有属性并进行劫持,初始化速度和内存占用都更优。
      • 惰性观察: 只有真正被访问到的嵌套属性才会被代理,而不是一次性递归完成。
      • 处理数组和新增属性: 无需像 Vue 2 那样进行特殊处理,所有操作(索引、length、新增属性)都是响应式的,性能一致且更优。

2. 代码组织和逻辑复用优化

这是对开发者体验影响最大的改进。

  • Composition API
    • 背景: Vue 2 的 Options API 在组件复杂时,同一功能的逻辑(数据、方法、计算属性、生命周期)会被拆分到不同选项中,导致代码难以阅读和维护。
    • 优势
      • 逻辑复用: 通过 setup 函数和一系列响应式 API(如 ref, reactive),可以将与同一功能相关的代码组织在一起,形成一个可复用的逻辑组合(Composable),彻底解决了 Vue 2 中 Mixins 带来的命名冲突和数据来源不清晰的问题。
      • 更好的 TypeScript 支持: 使用函数式的 API,对类型的推断非常自然和完美。
      • 更灵活的代码组织: 开发者可以像写普通函数一样组织代码,而不是被固定的选项所限制。

3. 更好的 TypeScript 支持

  • Vue 3 的代码库本身是用 TypeScript 重写的。
  • 这使得 Vue 3 提供了全面的、一流的 TypeScript 支持。无论是定义组件、Props,还是使用 Composition API,都能获得完美的类型推导和 IDE 支持,开发体验远超 Vue 2。

4. 新的组件和应用程序创建方式

  • Fragment: 组件可以拥有多个根节点,无需再包裹一个不必要的父级 div
  • Teleport: 可以将组件的一部分模板“传送”到 DOM 中其他位置,非常适合实现全局的 Modal、Dialog、Toast 等组件。
  • Suspense: 提供了内置的异步组件加载状态处理机制,可以在等待异步组件(如 async setup())时显示一个加载状态。
  • createApp: 新的应用程序实例创建方式,避免了 Vue 2 中全局 API(如 Vue.use, Vue.mixin)对测试的污染和全局配置的影响。

5. 其他重要更新

  • 废弃/移除的 API: 移除了 filter、内联模板 inline-template 等不常用的或容易引起困惑的特性。
  • 自定义渲染器 API: 提供了底层的、用于创建自定义渲染器的 API,使得 Vue 不仅可以渲染到 DOM,还可以渲染到 WebGL、Canvas、原生移动应用等。

总结

优化维度Vue 2Vue 3优化点
性能全量打包,递归劫持,全量 DiffTree-shakingProxyPatch Flag静态提升体积更小,速度更快
逻辑复用Mixins (有缺陷)Composition API高内聚,低耦合,类型友好
TypeScript需要额外装饰器,支持一般原生完美支持开发体验质的飞跃
代码组织Options API (逻辑分离)Composition API (逻辑聚合)更利于维护复杂组件
新特性Teleport, Suspense, Fragment解决特定场景痛点

总而言之,Vue 3 不是一次简单的增量更新,而是一次全面的、面向未来的升级。它在保持 Vue 2 核心思想(易上手、渐进式)的同时,通过底层架构的重构,解决了 Vue 2 在大型项目中的可维护性和性能瓶颈问题,并引入了现代化前端开发所必需的特性和开发体验。

以上就是我对 Vue 3 主要优化的理解。如果您对其中某个点感兴趣,我可以再深入展开。

谈谈对 Diff 算法 的理解

Diff 算法是虚拟 DOM 的核心,也是 Vue、React 等现代前端框架性能的关键所在。它负责在数据变化后,高效地计算出虚拟 DOM 树中需要更新的部分,从而最小化对真实 DOM 的操作。

下面我将从 是什么、为什么、核心思想、具体策略 以及 Vue 中的具体优化 这几个方面来详细阐述。


1. 什么是 Diff 算法?

Diff 算法,全称是 差异算法,它的核心任务是:对比新旧两棵虚拟 DOM 树,找出其中的差异,并计算出最小量的更新操作,然后应用到真实的 DOM 上。

关键前提:它基于两个假设,将时间复杂度从 O(n³) 优化到了 O(n):

  1. 相同类型的组件产生相似的 DOM 结构,不同类型的组件产生不同的 DOM 结构。
  2. 同一层级的子节点,可以通过唯一的 key 来稳定地标识它们。

2. 为什么需要 Diff 算法?

直接操作真实 DOM 的代价是非常高昂的。例如,一个列表中有 1000 项,其中只有 1 项数据发生了变化。如果没有 Diff 算法,我们可能需要:

  • 笨办法:清空整个列表的 DOM,然后重新创建 1000 个新的 DOM 节点并插入。
  • 智能办法:只找到那 1 个变化的项,并只更新它对应的 DOM。

Diff 算法就是实现这个“智能办法”的引擎。它通过 JavaScript 的计算 来换取 昂贵的 DOM 操作,因为 JavaScript 在内存中计算的速度远快于直接操作 DOM。


3. Diff 算法的核心思想与策略

Diff 算法并不是粗暴地整棵树比较,而是采用了一种 分层比较、深度优先 的策略。

核心策略一:只进行同层级比较,不跨级比较

算法只会对同一层级的节点进行比较,如果发现节点类型不同,则直接销毁旧节点及其整个子树,创建并插入新节点及其整个子树。这大大简化了比较的复杂度。

核心策略二:比较节点的自身属性

如果发现是同一个节点(标签名和 key 都相同),那么算法会进入一个 patch 过程,只更新该节点上发生变化的属性(如 class, style, id 等),而不会触碰它的子节点,从而节省了开销。


4. 子节点列表的比较(核心中的核心)

当新旧节点都有子节点时,如何高效地比较和更新子节点列表是 Diff 算法最复杂的部分。这里有一个经典的 双端比较算法

传统思路(无 key 或 简单 Diff):

  • 会按顺序逐个对比新旧子节点数组。
  • 问题:如果只是在列表头部插入一个新元素,会导致它后面的每一个元素都被认为发生了变化,从而进行不必要的更新或重新排序。

优化策略(带 key 的双端比较): Vue 和 React 都采用了类似的思路,Vue 的源码实现尤为典型。它会同时遍历新旧子节点数组的两个端点,通过四个指针来进行比较:

  1. 新前 vs 旧前:比较新开始节点和旧开始节点。
  2. 新后 vs 旧后:比较新结束节点和旧结束节点。
  3. 新后 vs 旧前:比较新结束节点和旧开始节点。(场景:将旧开始节点移动到末尾)
  4. 新前 vs 旧后:比较新开始节点和旧结束节点。(场景:将旧结束节点移动到开头)

这个过程可以概括为:

  • 通过这四种快捷方式,尽可能地 复用 已有的 DOM 节点,只进行 移动 操作,而不是销毁和创建。
  • 如果以上四种情况都不匹配,则会拿新节点的 key 去旧节点中寻找,如果找到了可复用的节点,就将其移动到正确的位置。
  • 如果最终在新列表中有未处理的节点,则 新建 DOM 节点;在旧列表中有未处理的节点,则 删除 对应的 DOM 节点。

举个例子(移动而非重建): 旧列表: [A, B, C, D] 新列表: [D, A, B, C]

双端 Diff 会通过比较发现,只需要将 D 节点从末尾移动到开头即可,而 A, B, C 节点都被完美复用了,无需任何更新。


5. key 的作用

现在我们可以更深刻地理解 key 的作用了:

  • 身份标识key 是 VNode 的唯一标识,它告诉 Diff 算法“在前后两次渲染中,哪些节点是同一个节点”。
  • 高效复用:有了稳定且唯一的 key,算法就能准确地找到可复用的节点,即使列表的顺序发生了剧烈变化,也能通过移动节点来高效更新,而不是销毁和重建。
  • 避免状态错乱:如果不使用 key 或使用 index 作为 key,在列表中间进行增删时,会导致节点复用错乱,从而引发一系列问题(如输入框内容错位、组件状态异常)。

为什么不能用 index 作为 key 因为 index 是不稳定的。当在列表头部插入一个新项时,所有后续项的 index 都发生了变化,导致 Diff 算法认为所有节点都发生了变化,从而触发大量的重新渲染和错误的节点复用,失去了 key 的优化意义。


6. Vue 3 在 Diff 算法上的优化

Vue 3 在编译阶段做了大量优化,使得运行时 Diff 的效率更高:

  1. 静态提升:将纯静态的节点提升到渲染函数之外,Diff 时直接跳过整个静态树。
  2. Patch Flag:在创建 VNode 时,会为其打上一个“补丁标志”,这个标志是一个数字,用位运算表示哪些属性是动态的(如 1 代表 TEXT2 代表 CLASS)。在 patch 阶段,Diff 算法只需要检查这些带有标志的动态节点和动态属性,无需再全量对比。
  3. Block Tree:Vue 3 引入了“块”的概念。一个“块”内部是一个动态节点数组。在更新时,只需要追踪块内的动态节点,减少了需要 Diff 的节点数量。

总结

Diff 算法是现代前端框架的“智能引擎”,它通过 同层比较、双端对比、key 标识 等策略,将虚拟 DOM 的对比复杂度降至 O(n),从而实现了高效更新。其核心思想是 尽可能地复用已有的 DOM 节点,通过移动来代替创建,以达到性能最优的目的。而 Vue 3 通过编译时优化(如 Patch Flag),进一步减少了运行时需要 Diff 的内容,将性能提升到了一个新的高度。

上次更新于: