一、Vue 2 響應式實現詳解
1. 核心代碼實現
// 依賴收集器(觀察者模式)
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. 使用步驟
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. 關鍵問題示例
// 動態屬性問題
data.newProp = 'test'; // ? 不會觸發更新
Vue.set(data, 'newProp', 'test'); // ? 正確方式// 數組索引修改問題
data.list[0] = 99; // ? 不會觸發
data.list.splice(0, 1, 99); // ? 正確方式
二、Vue 3 響應式實現詳解
1. 核心代碼實現
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. 使用步驟
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. 新數據結構支持
// 支持 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 開發技巧
// 解構保持響應式
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 響應式系統的演進,開發者不僅能寫出更高效的代碼,更能把握前端框架設計的核心思想。這種從「屬性劫持」到「代理攔截」的轉變,體現了前端技術追求更優雅、更高效的永恒主題。