文章目錄
- Vue2 響應式系統設計原理與實現
Vue2 響應式系統設計原理與實現
Vue2 的響應式原理主要基于以下幾點:
使用 Object.defineProperty () 方法對數據對象的屬性進行劫持
當數據發生變化時,通知依賴該數據的視圖進行更新
實現一個發布 - 訂閱模式,包含 Watcher(訂閱者)、Dep(依賴收集器)等核心概念
創建以下幾個核心部分:
Observer:遞歸地將數據對象的所有屬性轉換為響應式
Dep:依賴收集器,負責收集和通知訂閱者
Watcher:訂閱者,當數據變化時執行相應的回調函數
下面我們來簡單實現以下
// 依賴收集器類:管理某個數據的所有依賴(訂閱者)
class Dep {// 構造函數初始化constructor() {// 存儲所有訂閱者的數組this.subscribers = [];}// 添加訂閱者到收集器addSub(sub) {// 檢查訂閱者是否存在且有update方法if (sub && sub.update) {this.subscribers.push(sub);}}// 通知所有訂閱者數據已更新notify() {// 遍歷所有訂閱者并調用其update方法this.subscribers.forEach(sub => {sub.update();});}
}// 訂閱者類:代表一個依賴,數據變化時執行更新操作
class Watcher {// 構造函數:接收Vue實例、屬性名和回調函數constructor(vm, key, callback) {this.vm = vm; // 存儲Vue實例的引用this.key = key; // 要監視的數據屬性名this.callback = callback; // 數據變化時要執行的回調函數this.value = this.get(); // 初始化時獲取值,觸發getter完成依賴收集}// 獲取數據并將當前訂閱者添加到依賴收集器get() {// 將當前訂閱者設為Dep的目標,標記為當前需要收集的依賴Dep.target = this;// 訪問數據屬性,觸發其getter,從而完成依賴收集const value = this.vm[this.key];// 重置Dep.target,避免后續操作錯誤收集依賴Dep.target = null;// 返回獲取到的值return value;}// 數據變化時執行的更新方法update() {// 獲取新值const newValue = this.get();// 保存舊值const oldValue = this.value;// 只有當新舊值不同時才執行回調if (newValue !== oldValue) {// 更新當前值為新值this.value = newValue;// 調用回調函數,并將Vue實例作為上下文,傳入新值和舊值this.callback.call(this.vm, newValue, oldValue);}}
}// 將普通對象轉換為響應式對象的函數
function observe(data) {// 如果數據不是對象或為null,則無需處理if (!data || typeof data !== 'object') {return;}// 創建觀察者實例處理數據return new Observer(data);
}// 觀察者類:負責將對象的所有屬性轉換為響應式
class Observer {// 構造函數:接收需要處理的數據對象constructor(data) {this.data = data;// 遍歷對象屬性并處理this.walk(data);}// 遍歷對象的所有屬性walk(data) {// 獲取對象所有自有屬性的鍵名Object.keys(data).forEach(key => {// 為每個屬性定義響應式this.defineReactive(data, key, data[key]);});}// 核心方法:使用Object.defineProperty定義響應式屬性defineReactive(obj, key, val) {// 為當前屬性創建一個依賴收集器const dep = new Dep();// 如果屬性值是對象,遞歸處理使其也成為響應式observe(val);// 使用Object.defineProperty劫持屬性的getter和setterObject.defineProperty(obj, key, {enumerable: true, // 允許屬性被枚舉(例如在for...in循環中)configurable: true, // 允許屬性被配置(例如刪除屬性)// 當屬性被訪問時觸發的getterget() {// 如果當前有需要收集的依賴(Dep.target存在)if (Dep.target) {// 將當前依賴添加到收集器中dep.addSub(Dep.target);}// 返回屬性值return val;},// 當屬性被修改時觸發的setterset(newVal) {// 如果新值和舊值相同,則不做處理if (newVal === val) {return;}// 更新屬性值val = newVal;// 如果新值是對象,需要將其轉換為響應式observe(newVal);// 通知所有依賴當前屬性的訂閱者數據已更新dep.notify();}});}
}// 簡化版Vue類:整合響應式系統
class Vue {// 構造函數:接收配置選項constructor(options) {this.$options = options; // 存儲配置選項this.$data = options.data; // 存儲數據對象// 將數據轉換為響應式observe(this.$data);// 將data中的屬性代理到Vue實例上,方便直接訪問this.proxyData(this.$data);// 如果有created生命周期鉤子,執行它if (options.created) {options.created.call(this);}}// 數據代理方法:使vm.xxx等價于vm.$data.xxxproxyData(data) {// 遍歷data的所有屬性Object.keys(data).forEach(key => {// 在Vue實例上定義與data屬性同名的屬性Object.defineProperty(this, key, {// 當訪問vm.xxx時,返回vm.$data.xxx的值get() {return data[key];},// 當修改vm.xxx時,同步修改vm.$data.xxxset(newVal) {data[key] = newVal;}});});}// 提供$watch方法,用于監視數據變化$watch(key, callback) {// 創建一個新的訂閱者,關聯到指定的屬性和回調new Watcher(this, key, callback);}
}// 使用示例
const vm = new Vue({data: {message: 'Hello Vue',count: 0,user: {name: 'John'}},created() {console.log('初始化完成:', this.message);}
});// 添加監聽器,當message變化時觸發
vm.$watch('message', (newVal, oldVal) => {console.log(`message變化: ${oldVal} -> ${newVal}`);
});// 添加監聽器,當count變化時觸發
vm.$watch('count', (newVal, oldVal) => {console.log(`count變化: ${oldVal} -> ${newVal}`);
});// 添加監聽器,當user.name變化時觸發
vm.$watch('user.name', (newVal, oldVal) => {console.log(`user.name變化: ${oldVal} -> ${newVal}`);
});// 測試數據變化,觀察是否觸發更新
vm.message = 'Hello World'; // 觸發message的更新
vm.count = 1; // 觸發count的更新
vm.user.name = 'Jane'; // 觸發user.name的更新