目錄
一、什么是數據劫持?
二、核心 API:Object.defineProperty
三、Vue2 中的數據劫持實現
1. 對象屬性的劫持
2. 嵌套對象的處理
3. 數組的特殊處理
四、結合依賴收集的完整流程
五、數據劫持的局限性
六、Vue3 的改進方案
總結
一、什么是數據劫持?
數據劫持指的是攔截對象屬性的訪問和修改操作的能力。Vue2 通過 JavaScript 的?Object.defineProperty
?API 實現這一機制,在屬性被讀取或修改時執行自定義邏輯。
二、核心 API:Object.defineProperty
Object.defineProperty
?允許我們精確控制對象屬性的行為:
let obj = { name: 'Vue' };
let value = obj.name;Object.defineProperty(obj, 'name', {get() {console.log('讀取 name 屬性');return value;},set(newVal) {console.log('設置 name 屬性', newVal);value = newVal;}
});obj.name; // 控制臺輸出: "讀取 name 屬性"
obj.name = 'React'; // 控制臺輸出: "設置 name 屬性 React"
?
三、Vue2 中的數據劫持實現
1. 對象屬性的劫持
Vue 在初始化時會遍歷?data
?中的所有屬性:
function defineReactive(obj, key) {let value = obj[key];Object.defineProperty(obj, key, {get() {console.log(`讀取 ${key}`);return value;},set(newVal) {console.log(`更新 ${key}`, newVal);value = newVal;// 這里會觸發視圖更新}});
}const data = { message: 'Hello Vue' };
defineReactive(data, 'message');
?
2. 嵌套對象的處理
Vue 遞歸劫持嵌套對象:
function observe(data) {if (typeof data !== 'object' || data === null) return;new Observer(data);
}class Observer {constructor(value) {this.value = value;this.walk();}walk() {Object.keys(this.value).forEach(key => {defineReactive(this.value, key);});}
}function defineReactive(obj, key) {let value = obj[key];observe(value); // 遞歸劫持子屬性Object.defineProperty(obj, key, {// getter/setter 略});
}
3. 數組的特殊處理
由于?Object.defineProperty
?無法檢測數組索引變化,Vue 重寫了數組方法:
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {const original = arrayProto[method];arrayMethods[method] = function(...args) {console.log(`數組執行 ${method} 操作`);const result = original.apply(this, args);// 獲取新增的元素let inserted;switch(method) {case 'push':case 'unshift':inserted = args;break;case 'splice':inserted = args.slice(2);break;}// 劫持新增元素if (inserted) observeArray(inserted);// 觸發視圖更新return result;};
});function observeArray(items) {for (let item of items) {observe(item);}
}
?
四、結合依賴收集的完整流程
-
初始化階段:
-
遍歷?
data
?屬性,通過?defineProperty
?設置 getter/setter -
遞歸處理嵌套對象和數組
-
-
依賴收集:
get() {if (Dep.target) { // 當前正在計算的 Watcherdep.depend(); // 將 Watcher 添加到依賴列表}return value; }
3.派發更新:
set(newVal) {value = newVal;dep.notify(); // 通知所有 Watcher 更新 }
五、數據劫持的局限性
-
無法檢測屬性添加/刪除
this.obj.newProperty = 'value' // 非響應式
? ? 2. 數組索引和長度修改:
this.arr[0] = 'new' // 無法檢測
this.arr.length = 10 // 無法檢測
? ?3.解決方案:
this.$set(this.obj, 'newProperty', 'value')
this.$delete(this.obj, 'oldProperty')
六、Vue3 的改進方案
Vue3 使用?Proxy
?替代?Object.defineProperty
:
-
可直接監聽整個對象而非屬性
-
支持動態添加屬性
-
完美監聽數組變化
-
性能更優
const proxy = new Proxy(obj, {get(target, key) {// 攔截讀取操作},set(target, key, value) {// 攔截設置操作} });
總結
Vue2 的數據劫持機制通過?Object.defineProperty
?實現,結合依賴收集和派發更新,構建了響應式系統的核心。雖然存在一些局限性,但理解其原理有助于我們:
-
更好地使用 Vue 的響應式功能
-
避免常見的響應式陷阱
-
深入理解 Vue 的設計思想
掌握這些原理,能讓你在 Vue 開發中更加得心應手,寫出更高效、可維護的代碼!?
?
?