一、Vue 2 响应式实现详解
1. 核心代码实现
javascript
// 依赖收集器(观察者模式)
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => effect());
}
}
let activeEffect = null;
// 响应式转换核心
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
dep.depend(); // 收集依赖
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
dep.notify(); // 触发更新
}
}
});
}
// 递归处理对象
function observe(obj) {
Object.keys(obj).forEach(key => {
let value = obj[key];
if (typeof value === 'object' && value !== null) {
observe(value); // 递归处理嵌套对象
}
defineReactive(obj, key, value);
});
}
// 数组方法重写
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift'].forEach(method => {
const original = arrayProto[method];
arrayMethods[method] = function(...args) {
const result = original.apply(this, args);
this.__ob__.dep.notify(); // 手动触发更新
return result;
};
});
2. 使用步骤
javascript
const data = { count: 0, list: [1,2,3] };
observe(data);
// 定义响应式副作用
function watchEffect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
watchEffect(() => {
console.log('Count changed:', data.count);
});
data.count = 5; // 触发日志输出
data.list.push(4); // 触发更新
3. 关键问题示例
javascript
// 动态属性问题
data.newProp = 'test'; // ❌ 不会触发更新
Vue.set(data, 'newProp', 'test'); // ✅ 正确方式
// 数组索引修改问题
data.list[0] = 99; // ❌ 不会触发
data.list.splice(0, 1, 99); // ✅ 正确方式
二、Vue 3 响应式实现详解
1. 核心代码实现
javascript
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);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
effects && effects.forEach(effect => effect());
}
const reactiveMap = new WeakMap();
function reactive(obj) {
if (reactiveMap.has(obj)) return reactiveMap.get(obj);
const proxy = new Proxy(obj, {
get(target, key, receiver) {
track(target, key);
const res = Reflect.get(target, key, receiver);
if (typeof res === 'object' && res !== null) {
return reactive(res); // 惰性递归代理
}
return res;
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
trigger(target, key);
return true;
},
// 处理 delete 操作
deleteProperty(target, key) {
const hasKey = Object.prototype.hasOwnProperty.call(target, key);
const result = Reflect.deleteProperty(target, key);
if (hasKey && result) {
trigger(target, key);
}
return result;
}
});
reactiveMap.set(obj, proxy);
return proxy;
}
// 处理基本类型
function ref(value) {
return {
get value() {
track(this, 'value');
return value;
},
set value(newVal) {
value = newVal;
trigger(this, 'value');
}
};
}
2. 使用步骤
javascript
const state = reactive({ count: 0, items: ['apple'] });
const num = ref(10);
// 响应式副作用
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
effect(() => {
console.log('State changed:', state.count, num.value);
});
state.count++; // ✅ 触发更新
state.items.push('banana'); // ✅ 自动触发
num.value = 20; // ✅ 触发更新
三、技术对比全景图
特性 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
---|---|---|
动态属性检测 | 需手动调用 Vue.set |
自动检测 |
数组处理 | 需重写 7 个方法 | 原生支持所有操作 |
性能初始化 | O(n) 立即递归 | O(1) 惰性代理 |
嵌套对象处理 | 初始化时完全递归 | 按需代理 |
数据类型支持 | 对象/数组 | 支持 Map/Set/WeakMap 等 |
兼容性 | IE9+ | 不支持 IE11 及以下 |
内存占用 | 每个属性创建 Dep 实例 | 整个对象共享依赖映射 |
四、关键演进技术点
1. 依赖收集机制升级
-
Vue 2:每个属性对应一个 Dep 实例
-
Vue 3:通过
WeakMap
建立三级映射关系:targetMap: WeakMap<Target, depsMap> depsMap: Map<Key, dep> dep: Set<Effect>
2. 性能优化策略
- 惰性代理:只有访问到的属性才会被代理
- 缓存机制 :
reactiveMap
避免重复代理同一对象 - 批量更新:通过调度器合并多次数据变更
3. 新数据结构支持
javascript
// 支持 Map 的响应式
const mapState = reactive(new Map());
mapState.set('key', 'value'); // ✅ 自动触发更新
// 支持 Set 的操作
const setState = reactive(new Set());
setState.add(123); // ✅ 触发更新
五、最佳实践指南
Vue 2 项目注意事项
- 动态添加属性必须使用
Vue.set
- 数组操作优先使用变异方法
- 复杂数据结构建议提前初始化完整结构
Vue 3 开发技巧
javascript
// 解构保持响应式
const state = reactive({ x: 1, y: 2 });
const { x, y } = toRefs(state); // 使用 toRefs
// 组合式 API 示例
import { reactive, watchEffect } from 'vue';
export default {
setup() {
const state = reactive({ count: 0 });
watchEffect(() => {
console.log('Current count:', state.count);
});
return { state };
}
}
六、总结与展望
核心演进价值
- 开发体验:减少心智负担,代码更符合直觉
- 性能突破:大型应用响应式处理效率提升 2-5 倍
- 扩展能力:为未来响应式数据库等高级特性铺路
通过深入理解 Vue 响应式系统的演进,开发者不仅能写出更高效的代码,更能把握前端框架设计的核心思想。这种从「属性劫持」到「代理拦截」的转变,体现了前端技术追求更优雅、更高效的永恒主题。