7. nextTick的理解
7.1. Vue 的异步更新队列
- 数据变化不会立即更新 DOM:当数据变化时,Vue 将组件更新推入一个队列,并在同一事件循环(Event Loop)中缓冲所有数据变更。
- 合并重复操作:避免同一数据多次修改导致的重复渲染,提升性能。
7.2. nextTick
的作用
- 延迟回调执行:将回调函数推迟到下一个 DOM 更新周期之后执行,确保能访问到更新后的 DOM。
7.3. 实现机制
- 底层基于事件循环:Vue 内部优先使用微任务(Microtask):
-
- 支持环境:
Promise.then
→MutationObserver
→setImmediate
→setTimeout
(降级方案)
- 支持环境:
- 保证执行时机:在浏览器完成 DOM 更新后触发回调。
8. 你知道v-model的原理吗?说说看
8.1. 表单元素中的 v-model
8.1.1. 基础实现,input标签
<input v-model="message">
等价于
<input
:value="message"
@input="message = $event.target.value"
>
原理:
- 属性绑定:将数据
message
绑定到value
属性。 - 事件监听:监听
input
事件,更新数据到message
。
8.1.2. 其他表单元素的适配
<textarea>
:与<input>
相同。<select>
:监听change
事件。- 复选框(Checkbox)和单选框(Radio):处理
checked
属性和change
事件。
8.2. 组件中的 v-model
8.2.1. vue2
默认行为:
- Prop:
value
- 事件:
input
<!-- 父组件 -->
<ChildComponent v-model="message" />
等价于
<ChildComponent
:value="message"
@input="message = $event"
/>
自定义修改:
通过 model
选项可修改默认的 prop 和事件名:
// 子组件
export default {
model: {
prop: 'title', // 修改 prop 名为 title
event: 'change' // 修改事件名为 change
}
}
8.2.2. vue3
默认行为:
- Prop:
modelValue
- 事件:
update:modelValue
<!-- 父组件 -->
<ChildComponent v-model="message" />
等价于
<ChildComponent
:modelValue="message"
@update:modelValue="message = $event"
/>
8.3. 底层实现
8.3.1. 1. 数据 → 视图(响应式更新)
- 依赖追踪:通过响应式系统(如
Object.defineProperty
或Proxy
),当数据变化时,触发组件的重新渲染。 - 虚拟 DOM 对比:生成新的虚拟 DOM 并与旧 DOM 对比,更新变化的部分。
8.3.2. 2. 视图 → 数据(事件触发)
- 监听表单输入事件(如
input
、change
)。 - 在事件回调中更新数据,触发响应式系统的依赖通知。
数据变化 → 更新视图(响应式系统)
视图输入 → 触发事件 → 更新数据(事件监听)
9. Vue是怎样依赖收集的?
9.1. 核心概念
- 响应式对象
通过Object.defineProperty
(Vue 2) 或Proxy
(Vue 3) 劫持数据访问,将普通对象转为响应式对象。 - 依赖(Dep)
每个响应式属性对应一个Dep
实例,用于管理所有依赖该属性的观察者(Watcher)。 - 观察者(Watcher)
表示一个依赖关系,如组件渲染函数、计算属性或用户自定义的watch
。当数据变化时,Watcher
负责执行更新。
9.2. 依赖收集流程
9.2.1. Vue 2 的依赖收集(基于 Object.defineProperty)
-
- 步骤:
-
-
- 初始化响应式数据:
递归遍历对象,为每个属性创建Dep
并劫持getter/setter
。
- 初始化响应式数据:
-
function defineReactive(obj, key) {
const dep = new Dep(); // 每个属性一个 Dep
let value = obj[key];
Object.defineProperty(obj, key, {
get() {
if (Dep.target) { // 当前正在计算的 Watcher
dep.depend(); // 将 Watcher 添加到 Dep
}
return value;
},
set(newVal) {
value = newVal;
dep.notify(); // 通知所有 Watcher 更新
}
});
}
-
-
- 组件初始化时创建 Watcher:
每个组件实例对应一个渲染Watcher
,在mount
阶段首次执行渲染函数。 - 触发依赖收集:
渲染过程中访问数据属性,触发getter
,将当前Watcher
收集到属性的Dep
中。
- 组件初始化时创建 Watcher:
-
-
- 依赖关系示例:
// 数据
const data = { count: 0 };
// 转为响应式
observe(data);
// 组件 Watcher
new Watcher(() => {
console.log(data.count); // 访问 count,触发 getter
});
// 输出:0
data.count = 1; // 触发 setter,Watcher 重新执行
9.2.2. Vue 3 的依赖收集(基于 Proxy)
- 步骤:
-
- 创建响应式对象:
使用Proxy
拦截对象操作,无需递归初始化。
- 创建响应式对象:
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key); // 依赖收集
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
}
});
}
-
- 全局依赖管理:
通过track
函数将当前活动的effect
(类似 Watcher)记录到全局的targetMap
中。 - 依赖触发:
数据修改时,trigger
函数从targetMap
中找到依赖并执行。
- 全局依赖管理:
9.3. 注意
9.3.1. Dep 与 Watcher 的多对多关系
- Dep → Watcher:一个属性可被多个组件或计算属性依赖。
- Watcher → Dep:一个
Watcher
可能依赖多个属性(如模板中使用多个数据)。
9.3.2. 2. 避免重复收集
- Vue 2:通过
Dep.target
标识当前Watcher
,确保同一Watcher
不会重复添加到Dep
。 - Vue 3:使用
activeEffect
和effectStack
管理当前活动的副作用。
9.3.3. 3. 嵌套组件的依赖收集
- 父组件渲染时遇到子组件,会先完成子组件的依赖收集,再回到父组件。
9.4. 依赖更新的触发
9.4.1. 同步更新流程
graph TD
A[数据修改] --> B[触发 setter/Proxy set]
B --> C[Dep.notify / trigger]
C --> D[遍历所有 Watcher/Effect]
D --> E[执行 Watcher.run / effect scheduler]
E --> F[重新渲染或执行回调]
9.4.2. 异步更新流程
批量更新优化:
多次数据修改会合并到一次更新中,避免重复渲染。
9.5. 特殊场景处理
9.5.1. 数组的依赖收集
- Vue 2:重写数组方法(
push
、pop
等),在方法调用时手动触发通知。 - Vue 3:
Proxy
直接监听数组索引变化和方法调用。
9.5.2. 2. 计算属性与侦听器
- 计算属性:
惰性求值,只有当依赖变化时才重新计算。 - 侦听器(watch) :
显式指定依赖源,变化时触发回调。
10. 计算属性computed和watch的区别是什么?
- 计算属性(computed) :
-
- 用途:用于定义依赖于其他数据属性的计算值。
- 特点:
-
-
- 缓存:只有在依赖的数据变化时,才会重新计算。
- 自动更新:当依赖的数据变化时,计算属性会自动更新。
-
-
- 适用场景:需要基于其他数据生成的属性,例如全名、总价等。
- 监视器(watch) :
-
- 用途:用于在数据变化时执行特定的操作。
- 特点:
-
-
- 每次变化都会触发:无论数据是否变化,只要被监视的数据变化,就会执行回调函数。
- 无缓存:不进行缓存,每次变化都会执行逻辑。
-
-
- 适用场景:需要在数据变化时执行副作用操作,比如调用API、修改其他状态等。
- 主要区别:
-
- 缓存机制:计算属性有缓存,只有依赖变化时才重新计算;监视器每次变化都会执行。
- 执行方式:计算属性是被动更新,依赖变化自动触发;监视器是主动执行,变化触发回调函数。
- 应用场景:计算属性适合生成依赖属性,监视器适合执行复杂逻辑或副作用操作。
11. Vue中的computed如何监听数组的变化?
在 Vue 中,computed
属性可以用来监听数据的变化并根据依赖的数据进行计算。然而,默认情况下,computed
并不会直接监听数组的变化,除非数组引用发生变化(例如,整个数组被替换)。具体来说:
11.1. 数组的变化类型
- 引用变化:如果数组的引用发生变化(例如,
array = newArray
),computed
会触发重新计算。 - 元素变化:如果数组中的元素发生变化(例如,修改数组中的某个项),
computed
默认不会触发重新计算
11.2. computed监听数组变化
11.2.1. 方法一:使用数组变更方法
Vue 提供了一些数组变更方法(如 push
、pop
、shift
、unshift
、splice
等),这些方法会触发 Vue 的响应式更新机制,从而让 computed
属性重新计算。
11.2.2. 使用 watch
监听数组变化
如果你需要在数组变化时执行更复杂的逻辑,可以使用 watch
来监听数组的变化。
11.2.3. 使用 Vue.set
或 this.$set
如果你直接修改数组中的某个元素(而不是使用数组变更方法),可以使用 Vue.set
或 this.$set
来触发响应式更新。
12. 强制更新组件的方法有哪些?分别有何利弊?
12.1. this.$forceUpdate()
12.1.1. 原理
强制当前组件实例重新渲染,跳过虚拟 DOM 的 Diff 过程,直接调用 render
函数生成新 VNode 并更新 DOM。
12.1.2. 使用方式
// 选项式 API
this.$forceUpdate();
// 组合式 API(Vue 3)
import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
instance.proxy.$forceUpdate();
12.1.3. 优点
- 简单直接:无需修改数据或结构
- 局部更新:仅影响当前组件及其子组件
12.1.4. 缺点
- 违背响应式原则:掩盖数据未正确响应的设计问题
- 性能损耗:强制全组件树重新渲染,可能引发不必要的子组件更新
- 不解决根本问题:未修复数据响应性,后续更新仍需手动触发
12.2. 修改组件的 key
12.2.1. 原理
通过改变 key
值,使 Vue 认为组件已销毁并重新创建,触发完整生命周期。
12.2.2. 使用方式
// 修改 key 值强制重建组件
this.componentKey = Date.now(); // 或递增计数器
12.2.3. 优点
- 彻底重置组件状态:适合需要完全重新初始化的场景
- 兼容性强:适用于任何组件和复杂场景
12.2.4. 缺点
- 性能开销大:销毁和重建组件成本高,尤其是复杂组件
- 状态丢失:组件内部状态(如表单输入)会被重置
- 设计异味:可能反映组件设计存在耦合问题
12.3. 利用 v-if
切换
12.3.1. 原理
通过 v-if="show"
控制组件挂载/卸载,切换时重新渲染。
12.3.2. 使用方式
// 先卸载再重新挂载
this.show = false;
this.$nextTick(() => {
this.show = true;
});
12.3.3. 优点
- 控制灵活:可精确控制重新渲染时机
- 类似 ****
key
****的效果:但保留父组件状态
12.3.4. 缺点
- 代码冗余:需要手动处理状态保留逻辑
- 闪烁问题:快速切换可能导致界面闪烁
- 不适用于保持内部状态:与
key
类似,会丢失子组件状态
12.4. 强制更新依赖(响应式系统)
12.4.1. 原理
通过修改响应式数据的引用地址,触发依赖更新。
12.4.2. 使用方式
// 对象:替换整个对象
this.obj = { ...this.obj };
// 数组:返回新数组
this.arr = this.arr.slice();
12.4.3. 优点
- 符合响应式规范:利用 Vue 的响应式机制
- 精准更新:仅触发相关依赖的重新计算
12.4.4. 缺点
- 仅适用于引用类型:基础数据类型无法使用
- 可能引发副作用:若其他代码依赖原引用,可能导致意外行为
12.5. 使用 Vue.set
/ this.$set
12.5.1. 原理
显式声明新增属性为响应式,适用于动态添加未预先声明的属性。
12.5.2. 使用方式
// Vue 2
this.$set(this.obj, 'newKey', 'value');
// Vue 3(通常不需要,Proxy 自动检测)
import { set } from 'vue';
set(this.obj, 'newKey', 'value');
12.5.3. 优点
- 修复数据响应性:根本解决视图不更新的问题
- 官方推荐:符合 Vue 设计模式
12.5.4. 缺点
- 局限性:仅解决新增属性/数组索引的响应性问题
- 需明确问题根源:无法修复其他原因导致的更新失败
12.6. 总结与最佳实践
方法 | 适用场景 | 推荐指数 | 注意事项 |
---|---|---|---|
$forceUpdate() | 紧急修复、非响应式数据依赖 | ⭐⭐ | 避免滥用,排查数据问题 |
修改 key | 需要完全重置组件状态 | ⭐⭐ | 性能敏感场景慎用 |
v-if 切换 | 临时强制重建组件 | ⭐ | 可能导致闪烁 |
强制更新依赖 | 引用类型数据未触发更新 | ⭐⭐⭐ | 优先选择的响应式方案 |
Vue.set | 动态添加属性/数组项未响应 | ⭐⭐⭐⭐ | Vue 2 必备,Vue 3 少用 |