Vue 2与Vue 3响应式原理及实际应用详解
作者:小熊猫写前端
Vue.js的响应式系统是其核心特性之一,它使得数据和视图能够自动保持同步,这篇文章主要介绍了Vue2与Vue3响应式原理及实际应用的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
Vue 2 与 Vue 3 响应式原理详解
深入理解 Vue 响应式系统的核心机制,对比 Vue 2 和 Vue 3 的实现差异
Vue 2 响应式原理
核心机制:Object.defineProperty
Vue 2 使用 Object.defineProperty 拦截对象属性的访问和修改,这是其响应式系统的基石。
实现流程
1. 初始化阶段
// 伪代码示例
function defineReactive(obj, key, val) {
const dep = new Dep(); // 每个属性对应一个依赖收集器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
if (Dep.target) {
dep.addSub(Dep.target); // 将当前 Watcher 添加到 Dep 中
}
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
// 派发更新
dep.notify(); // 通知所有 Watcher 更新
}
}
});
}
关键组件:
- Dep(依赖收集器):每个属性对应一个 Dep,用于存储所有依赖该属性的 Watcher
- Watcher(观察者):每个组件实例对应一个 Watcher,负责执行更新函数
2. 依赖收集(getter)
当访问响应式属性时,会触发 getter:
get() {
// 将当前 Watcher 添加到 Dep 中
Dep.target && dep.addSub(Dep.target);
return value;
}
流程说明:
- 组件渲染时访问 data 中的属性
- 触发 getter
- 将当前 Watcher(组件实例)添加到该属性的 Dep 中
- 建立依赖关系
3. 派发更新(setter)
当修改响应式属性时,会触发 setter:
set(newVal) {
if (newVal !== value) {
value = newVal;
// 通知所有 Watcher 更新
dep.notify();
}
}
流程说明:
- 修改属性值
- 触发 setter
- 调用
dep.notify()通知所有依赖该属性的 Watcher - Watcher 执行更新函数,触发组件重新渲染
4. 更新视图
Watcher 收到通知后执行更新函数,触发组件重新渲染。
局限性
1. 无法监听数组索引和长度变化
// Vue 2 中无法监听到这些变化 this.list[0] = 'new value'; // ❌ 不会触发更新 this.list.length = 0; // ❌ 不会触发更新
解决方案: Vue 2 重写了数组的 7 个方法:
pushpopshiftunshiftsplicesortreverse
// 只有通过这些方法才能触发更新
this.list.push('new item'); // ✅ 会触发更新
this.list.splice(0, 1, 'new value'); // ✅ 会触发更新
2. 无法监听对象属性的新增/删除
// Vue 2 中无法监听到新增属性 this.obj.newProperty = 'value'; // ❌ 不会触发更新 delete this.obj.property; // ❌ 不会触发更新
解决方案: 使用 Vue.set 或 this.$set
// 正确的方式 Vue.set(this.obj, 'newProperty', 'value'); // 或 this.$set(this.obj, 'newProperty', 'value');
3. 性能问题
- 需要递归遍历所有属性,初始化性能较差
- 对于深层嵌套对象,初始化开销较大
Vue 3 响应式原理
核心机制:Proxy
Vue 3 使用 Proxy 代理整个对象,可以拦截更多操作,提供了更强大的响应式能力。
实现流程
1. 创建响应式对象
// reactive 使用 Proxy
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 派发更新
trigger(target, key);
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
trigger(target, key);
return result;
},
has(target, key) {
track(target, key);
return Reflect.has(target, key);
}
// 还可以拦截更多操作...
});
}
关键优势:
- 可以拦截对象的所有操作(get、set、delete、has 等)
- 不需要递归遍历,按需处理
- 支持数组索引和长度变化
- 支持对象属性的新增和删除
2. 依赖收集(track)
使用 WeakMap 存储依赖关系:
// 依赖存储结构
// WeakMap<target, Map<key, Set<effect>>>
const targetMap = new WeakMap();
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
数据结构说明:
WeakMap<target, Map<key, Set<effect>>>- 每个响应式属性对应一个
Set,存储所有依赖它的effect - 使用
WeakMap避免内存泄漏
3. 派发更新(trigger)
找到该属性对应的所有 effect 并执行:
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => {
effect();
});
}
}
4. Effect 系统
effect 是响应式更新的执行单元:
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn(); // 执行时自动收集依赖
activeEffect = null;
}
Effect 的应用:
computed会创建 effectwatch会创建 effect- 组件渲染函数会创建 effect
- 执行时自动收集依赖
优势
1. 可监听数组索引和长度变化
const list = reactive([1, 2, 3]); list[0] = 10; // ✅ 自动触发更新 list.length = 0; // ✅ 自动触发更新
2. 可监听对象属性的新增/删除
const obj = reactive({ name: 'Vue3' });
obj.age = 18; // ✅ 自动响应式
delete obj.name; // ✅ 自动触发更新
3. 性能更好
- 按需收集依赖(懒收集):只有访问到的属性才会建立依赖关系
- 使用 WeakMap 避免内存泄漏:WeakMap 的键是弱引用
- 支持批量更新优化:可以合并多个更新操作
核心对比总结
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 核心 API | Object.defineProperty | Proxy |
| 监听范围 | 对象属性(需递归) | 整个对象 |
| 数组监听 | 需重写方法 | 原生支持 |
| 新增属性 | 需 Vue.set | 自动支持 |
| 删除属性 | 需 Vue.delete | 自动支持 |
| 性能 | 初始化时递归遍历 | 按需收集,性能更好 |
| 依赖存储 | Dep + Watcher | WeakMap + Set + effect |
| 内存占用 | 需要为每个属性创建 Dep | 按需创建,更节省内存 |
| 拦截能力 | 只能拦截 get/set | 可拦截多种操作 |
实际应用示例
Vue 2 示例
export default {
data() {
return {
obj: { name: 'Vue2' },
list: [1, 2, 3]
};
},
methods: {
addProperty() {
// ❌ 错误:不会触发更新
// this.obj.age = 18;
// ✅ 正确:需要使用 $set
this.$set(this.obj, 'age', 18);
},
updateArray() {
// ❌ 错误:不会触发更新
// this.list[0] = 10;
// ✅ 正确:需要使用数组方法
this.list.splice(0, 1, 10);
}
}
};
Vue 3 示例
import { reactive, ref } from 'vue';
export default {
setup() {
const obj = reactive({ name: 'Vue3' });
const list = reactive([1, 2, 3]);
const addProperty = () => {
// ✅ 直接添加即可响应式
obj.age = 18;
};
const updateArray = () => {
// ✅ 直接修改即可响应式
list[0] = 10;
list.length = 5; // ✅ 也支持
};
return {
obj,
list,
addProperty,
updateArray
};
}
};
Composition API 示例
import { reactive, ref, computed, watch } from 'vue';
export default {
setup() {
// reactive 用于对象
const state = reactive({
count: 0,
name: 'Vue3'
});
// ref 用于基本类型
const message = ref('Hello');
// computed 自动创建 effect
const doubleCount = computed(() => state.count * 2);
// watch 自动创建 effect
watch(() => state.count, (newVal, oldVal) => {
console.log(`count changed: ${oldVal} -> ${newVal}`);
});
return {
state,
message,
doubleCount
};
}
};
核心概念补充
Vue 3 的三种响应式 API
1. reactive
用于对象,返回 Proxy 代理对象:
const obj = reactive({ name: 'Vue3', age: 18 });
// 可以直接访问和修改
obj.name = 'Vue 3.0';
obj.city = 'Beijing'; // ✅ 新增属性自动响应式
2. ref
用于基本类型,返回包含 .value 的对象(内部也是 Proxy):
const count = ref(0);
// 在 JavaScript 中需要使用 .value
count.value = 10;
// 在模板中自动解包
// <div>{{ count }}</div> 不需要 .value
ref 也可以用于对象:
const obj = ref({ name: 'Vue3' });
obj.value.name = 'Vue 3.0'; // 需要 .value
3. readonly
创建只读的响应式对象:
const state = reactive({ count: 0 });
const readonlyState = readonly(state);
readonlyState.count = 10; // ❌ 警告:无法修改只读属性
响应式流程总结
数据变化 ↓ Proxy 拦截 ↓ trigger 触发 ↓ 找到所有 effect ↓ 执行更新 ↓ 重新渲染
完整示例:理解响应式流程
import { reactive, effect } from 'vue';
// 1. 创建响应式对象
const state = reactive({ count: 0, name: 'Vue3' });
// 2. 创建 effect(类似组件的渲染函数)
effect(() => {
// 访问响应式属性时会自动收集依赖
console.log(`Count: ${state.count}, Name: ${state.name}`);
});
// 3. 修改数据
state.count = 1; // ✅ 自动触发 effect 执行
state.name = 'Vue 3.0'; // ✅ 自动触发 effect 执行
state.age = 18; // ✅ 新增属性也会触发(如果 effect 中访问了它)
// 输出:
// Count: 0, Name: Vue3 (初始执行)
// Count: 1, Name: Vue3 (count 变化触发)
// Count: 1, Name: Vue 3.0 (name 变化触发)
总结
Vue 2 和 Vue 3 的响应式系统在核心实现上有本质区别:
- Vue 2 使用
Object.defineProperty,需要递归遍历对象属性,存在一些局限性 - Vue 3 使用
Proxy,可以拦截更多操作,性能更好,功能更强大
理解这些差异有助于:
- 更好地使用 Vue 3 的新特性
- 避免常见的响应式问题
- 优化应用性能
- 深入理解 Vue 的工作原理
参考资源:
到此这篇关于Vue2与Vue3响应式原理及实际应用详解的文章就介绍到这了,更多相关Vue2与Vue3响应式原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
