高频面试题中的Vue(补充中...)

稀饭2025-01-16interviewVue

Vue2/Vue3生命周期区别

  1. 语法上用on作为前缀;
  2. 创建前后的钩子替换为了setup;
  3. 销毁前的钩子替换为了onBeforeUnmount;
  4. 销毁后的钩子替换为了onUnmounted;

// 创建前后
beforeCreate -> setup()
created -> setup()
// 挂载前后
beforeMount -> onBeforeMount
mounted -> onMounted
// 更新前后
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
// 销毁前后
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
// 错误捕获
errorCaptured -> onErrorCaptured

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

  1. 数据隔离:保证每个组件实例的数据独立,避免共享数据导致的相互影响。
  2. 响应式支持:函数返回的新对象可以被Vue的响应式系统单独追踪。
  3. 灵活性:允许根据上下文动态生成数据。
  4. 性能优化:避免深拷贝操作,提高性能。

Vue3相对于Vue2做了哪些优化

  1. 重写数据劫持,使用proxy;
  2. 静态节点标记,会区分静态节点和动态节点,只对比动态节点,vue2是全量对比;
  3. 静态提升,不参与更新的节点,会做静态提升,只被创建一次,渲染时直接复用;
  4. 缓存事件处理函数,对事件处理函数进行了缓存,避免了每次渲染时都需要重新创建处理函数;
  5. 移除多余api;
  6. 支持Composition API;

Vue3 Proxy响应式系统的优化

  1. 依赖按需收集Vue2 在初始化时遍历整个对象,而 Vue 3 只有在 访问属性时才进行代理,减少性能消耗。
  2. 自动清理无效依赖 Vue 3 采用 WeakMap + Set 进行依赖存储,避免内存泄漏,Vue 2 需要手动管理依赖删除。
  3. 只更新受影响的组件 Vue 3 的 trigger() 机制让每个组件只更新它所依赖的部分,避免 Vue 2 中全局重新计算的问题。

总结

Proxy 使 Vue 3 的响应式系统更高效,支持新增属性监听、数组操作拦截等。依赖追踪采用 WeakMap + Set 存储,提高性能,避免 Vue 2 的内存泄漏问题。Vue 3 采用 Lazy Proxy(惰性代理),只有访问属性时才进行代理,减少初始化开销。Vue 3 的响应式机制通过effect() 进行自动依赖收集,让数据更新更智能、更高效。

https://mp.weixin.qq.com/s/upNj7bRK1ZuVx0wHJ1aKNgopen in new window

讲一下Vue3相比Vue2,它在diff算法上做了哪些优化?

  • 1. 静态节点提升,在编译阶段,会将静态节点提升到渲染函数之外。这些节点在后续的渲染中会被完全跳过 diff过程,直接复用。

  • 2. 基于最长递增子序列的优化Vue3在双端比较之后的中间乱序部分,基于动态规划的思想,使用贪心算法和二分查找最长递增子序列,找出需要移动的最小元素集合,最大程度地复用DOM节点。

  • 3. Patch Flags (补丁标志),为每个动态节点生成一个二进制的标志位,即Patch Flags;这些标志位表示了该节点可能发生的变化。在diff过程中,Vue3会首先检查节点的Patch Flags。如果一个节点没有Patch Flags,说明它是静态节点,直接跳过diff。如果一个节点有Patch Flags,则根据标志位来判断需要进行哪些比较和更新。例如,如果一个节点的Patch Flags只包含TEXT,那么只需要比较和更新文本内容即可,无需比较其他属性或子节点。

更多内容

Vue的双端比较算法

Vue的双端比较算法是一种优化虚拟DOM diff过程的策略。它通过同时从新旧虚拟DOM树的两端开始比较,向中间遍历来查找差异,使得查找的效率更高。

具体来说,双端比较算法的过程如下:

  1. 在旧虚拟DOM树和新虚拟DOM树的两端分别设置两个指针,分别指向首尾节点。

  2. 比较两个指针所指向的节点是否相同,如果相同则继续比较下一个节点,否则转到步骤3。

  3. 判断当前的节点是否为常规节点(例如div、p等),如果是则进一步比较子节点,否则直接将旧节点替换为新节点。

  4. 每次比较时,先尝试将旧指针向前移动一步,再比较两个指针所指向的节点是否相同。如果相同则重复步骤2,否则将旧指针回退到原位置,并尝试将新指针向后移动一步。如果相同则重复步骤2,否则将新节点插入到DOM中。

  5. 当新旧指针重合时,比较结束,剩余的未处理节点会被直接插入或删除。

通过使用双端比较算法,Vue可以在虚拟DOM diff过程中,尽可能地减少需要比较的节点数,从而提高性能。同时,由于该算法只是为了优化diff过程,因此仍然需要使用Key属性来帮助Vue跟踪节点的身份,以避免不必要的DOM操作。

Vue的响应式原理

  • 数据劫持/代理

    defineProperty/proxy

  • 依赖收集

    当数据被访问(触发getter)时,会进行依赖收集,存在deps中,用于管理所有依赖该属性的Watcher

  • Dep的核心方法:

    • depend():添加当前活跃的Watcher到依赖中。
    • notify():数据更新后通知所有依赖的Watcher。 当数据被修改(触发setter)时,会触发更新逻辑。
  • 依赖更新

    通过Dep.notify来通知Watcher更新。

    Dep遍历所有依赖的Watcher,逐个调用Watcherupdate() 方法,Watcher被标记为“脏”状态,推入更新队列。

    Vue合并同一Watcher,确保每个Watcher只会执行一次。 在下一次事件循环(通过Promise.thensetTimeout)中统一处理更新队列。

    调用Watcherrun()方法,执行对应的渲染逻辑(会调用组件的更新函数)或回调函数。

  • DOM更新

    • 虚拟DOM更新 渲染Watcher调用组件的渲染函数,生成新的虚拟DOMVue对比新旧虚拟DOM(Diff算法),计算出最小的DOM修改操作。
    • 真实DOM更新 根据Diff算法的结果,Vue更新最小范围的真实DOM,完成页面更新。

Vue的diff更新过程

创建VNode:

  • Vue.js 通过编译器将模板转换为渲染函数,或直接使用手写的渲染函数。
  • 渲染函数执行后返回一个 VNode 树,每个 VNode 对象描述了一个真实 DOM 节点,并包含其标签名、属性、子节点等信息。

比较VNode:

  • Vue.js会将新旧两个 VNode 树进行比较,找出需要更新的节点。这个比较过程是基于双指针算法,同时进行深度优先遍历,查找差异。
  • Vue.js会使用一个索引记录每个节点在新旧 VNode 树中的位置,以便快速对比。
  • 首先会比较根节点,判断它们是否相同。如果不同,直接替换整个根节点。
  • 如果根节点相同,则递归比较子节点,查找差异。
  • 相同节点的比较:如果新旧两个 VNode 对象的 key 和标签名相同,Vue.js 会认为它们是相同节点,然后进一步比较其属性和子节点。
  • 不同节点的比较:如果新旧两个 VNode 对象不相同,Vue.js 会直接替换旧节点为新节点。

应用更新:

  • 在比较过程中,Vue.js 会记录需要更新的节点,将这些变更应用到真实的 DOM 上,完成视图的更新。
  • Vue.js 会尽量复用已存在的 DOM 节点,减少不必要的 DOM 操作,提高性能。 如果存在无法复用的节点,Vue.js 会销毁旧节点,并创建新节点,替换到正确的位置。

Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?

判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。

template怎么生成真实dom

Vue的模版编译过程主要如下:template -> ast -> render函数 -> 虚拟DOM -> 真实DOM

  1. 读取模板: Vue 会读取 HTML 模板并将其转换为字符串。
  2. 解析模板: Vue 使用编译器将字符串模板转换为抽象语法树(AST),其中包含模板中的每个元素和它们的属性。
  3. 生成 render 函数: Vue 使用抽象语法树生成 render 函数。
  4. 数据响应: Vue 将数据绑定到 render 函数,并使用 Object.defineProperty 监听数据的变化,在数据更改时重新生成 render 函数。
  5. 虚拟 DOM: Vue 通过 render 函数生成虚拟 DOM,该数据结构是真实 DOM 的内存版本。
  6. 更新 DOM: Vue 通过对比虚拟 DOM 和真实 DOM 的差异,仅更新需要更新的部分,从而生成最终的真实 DOM。

原文链接:https://blog.csdn.net/qq_38261819/article/details/129038001open in new window

Vue3多节点怎么实现的

是通过 Vue 的编译器将模板中的多个根节点转换为一个片段,并生成相应的渲染函数。在运行时,Vue 的渲染器会正确处理这个片段,并将其渲染到页面上

为什么Vue没有fiber

Vue 使用了 Object.defineProperty 或 Proxy 来劫持数据的访问和修改操作,以便能够追踪依赖并在数据变化时触发相应的更新。当数据发生改变时,Vue 会通过异步的 nextTick 机制将视图的更新推迟到下一个事件循环中,以此来减少不必要的重复渲染

Vue的nextTick是怎么实现的?

如何降级兼容

vue3的setup做了什么

router路由的原理

slot插槽类型,原理实现

scoped原理

为什么Vue3中用reflect来操作数据

Reflect 是为了在执行对应的拦截操作的方法时能 传递正确的 this 上下文

Vuex的原理

Vuex和pina通知数据更新

Vuex

created() {
  this.unsubscribe = this.$store.subscribe((mutation, state) => {
    console.log('mutation type:', mutation.type)
    console.log('new state:', state)
  })
},
beforeDestroy() {
  this.unsubscribe() // 取消订阅
}

pina

const store = useStore()

// 订阅状态变化
store.subscribe((mutation, state) => {
  console.log('mutation type:', mutation.type)
  console.log('new state:', state)
  
  // 在这里可以执行页面数据更新的操作
})

Vue3中watch和watchEffect的区别

Vue3中reactive和ref的区别

reactive: 只能用于对象类型(对象、数组、Map、Set等) 不能处理原始类型(string、number、boolean等)

ref: 可以包装任何类型的数据 对原始类型和对象类型都适用

reactive: 直接使用Proxy对对象进行响应式代理 深层代理,会递归处理嵌套对象 ref: 将值包装在一个带有value属性的对象中 对value属性使用getter/setter实现响应式 如果value是对象,内部会调用reactive进行处理

Last Updated 6/12/2025, 9:48:56 AM