高频面试题中的Vue(补充中...)
Vue2/Vue3生命周期区别
- 语法上用
on
作为前缀; - 创建前后的钩子替换为了
setup
; - 销毁前的钩子替换为了
onBeforeUnmount
; - 销毁后的钩子替换为了
onUnmounted
;
// 创建前后
beforeCreate -> setup()
created -> setup()
// 挂载前后
beforeMount -> onBeforeMount
mounted -> onMounted
// 更新前后
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
// 销毁前后
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
// 错误捕获
errorCaptured -> onErrorCaptured
为什么 Vue 组件中的 data 是一个函数?
- 数据隔离:保证每个组件实例的数据独立,避免共享数据导致的相互影响。
- 响应式支持:函数返回的新对象可以被
Vue
的响应式系统单独追踪。 - 灵活性:允许根据上下文动态生成数据。
- 性能优化:避免深拷贝操作,提高性能。
Vue3相对于Vue2做了哪些优化
- 重写数据劫持,使用proxy;
- 静态节点标记,会区分静态节点和动态节点,只对比动态节点,vue2是全量对比;
- 静态提升,不参与更新的节点,会做静态提升,只被创建一次,渲染时直接复用;
- 缓存事件处理函数,对事件处理函数进行了缓存,避免了每次渲染时都需要重新创建处理函数;
- 移除多余api;
- 支持Composition API;
Vue3 Proxy响应式系统的优化
- 依赖按需收集Vue2 在初始化时遍历整个对象,而 Vue 3 只有在 访问属性时才进行代理,减少性能消耗。
- 自动清理无效依赖 Vue 3 采用 WeakMap + Set 进行依赖存储,避免内存泄漏,Vue 2 需要手动管理依赖删除。
- 只更新受影响的组件 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/upNj7bRK1ZuVx0wHJ1aKNg
讲一下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树的两端开始比较,向中间遍历来查找差异,使得查找的效率更高。
具体来说,双端比较算法的过程如下:
在旧虚拟DOM树和新虚拟DOM树的两端分别设置两个指针,分别指向首尾节点。
比较两个指针所指向的节点是否相同,如果相同则继续比较下一个节点,否则转到步骤3。
判断当前的节点是否为常规节点(例如div、p等),如果是则进一步比较子节点,否则直接将旧节点替换为新节点。
每次比较时,先尝试将旧指针向前移动一步,再比较两个指针所指向的节点是否相同。如果相同则重复步骤2,否则将旧指针回退到原位置,并尝试将新指针向后移动一步。如果相同则重复步骤2,否则将新节点插入到DOM中。
当新旧指针重合时,比较结束,剩余的未处理节点会被直接插入或删除。
通过使用双端比较算法,Vue可以在虚拟DOM diff过程中,尽可能地减少需要比较的节点数,从而提高性能。同时,由于该算法只是为了优化diff过程,因此仍然需要使用Key属性来帮助Vue跟踪节点的身份,以避免不必要的DOM操作。
Vue的响应式原理
数据劫持/代理
defineProperty
/proxy
依赖收集
当数据被访问(触发getter)时,会进行依赖收集,存在deps中,用于管理所有依赖该属性的
Watcher
;Dep的核心方法:
depend()
:添加当前活跃的Watcher
到依赖中。notify()
:数据更新后通知所有依赖的Watcher
。 当数据被修改(触发setter)时,会触发更新逻辑。
依赖更新
通过
Dep.notify
来通知Watcher
更新。Dep
遍历所有依赖的Watcher
,逐个调用Watcher
的update()
方法,Watcher
被标记为“脏”状态,推入更新队列。Vue
合并同一Watcher
,确保每个Watcher
只会执行一次。 在下一次事件循环(通过Promise.then
或setTimeout
)中统一处理更新队列。调用
Watcher
的run()
方法,执行对应的渲染逻辑(会调用组件的更新函数)或回调函数。DOM更新
- 虚拟DOM更新 渲染
Watcher
调用组件的渲染函数,生成新的虚拟DOM
。Vue
对比新旧虚拟DOM
(Diff算法),计算出最小的DOM
修改操作。 - 真实DOM更新 根据
Diff
算法的结果,Vue
更新最小范围的真实DOM
,完成页面更新。
- 虚拟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
- 读取模板: Vue 会读取 HTML 模板并将其转换为字符串。
- 解析模板: Vue 使用编译器将字符串模板转换为抽象语法树(AST),其中包含模板中的每个元素和它们的属性。
- 生成 render 函数: Vue 使用抽象语法树生成 render 函数。
- 数据响应: Vue 将数据绑定到 render 函数,并使用 Object.defineProperty 监听数据的变化,在数据更改时重新生成 render 函数。
- 虚拟 DOM: Vue 通过 render 函数生成虚拟 DOM,该数据结构是真实 DOM 的内存版本。
- 更新 DOM: Vue 通过对比虚拟 DOM 和真实 DOM 的差异,仅更新需要更新的部分,从而生成最终的真实 DOM。
原文链接:https://blog.csdn.net/qq_38261819/article/details/129038001
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进行处理