Vue2作為 MVVM框架
/*
Vue2 通過 Object.defineProperty 監聽、挾持數據,實現響應式
并通過 Dep(依賴收集器) 和 Watcher 實現依賴收集,通知視圖更新
*//*
但是 Vue2用Object.defineProperty 無法監聽新增屬性、無法監聽數組索引變化、無法監聽數組長度變化
*/// 依賴收集器
class Dep {constructor() {this.subs = [];}addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => sub.update());}
}Dep.target = null;// Observer 數據劫持
// 激活 get,set 函數
function Vue2Reactive(obj, key, val) {const dep = new Dep();Object.defineProperty(obj, key, {get() {// 依賴收集if (Dep.target) {dep.addSub(Dep.target);}return val;},set(newVal) {if (newVal !== val) {val = newVal;// 通知視圖更新dep.notify();}}});
}
// 通過 Object.keys(obj) + forEach 遍歷對象的所有屬性都進行激活
function activeAllProperties(obj) {if (typeof obj !== 'object' || obj === null) return;Object.keys(obj).forEach(key => {Vue2Reactive(obj, key, obj[key]);});
}// Watcher 類
class Watcher {constructor(vm, key, callback) {this.vm = vm;this.key = key;this.callback = callback;this.value = this.get();}get() {Dep.target = this;const value = this.vm[this.key];Dep.target = null;return value;}update() {const newValue = this.vm[this.key];if (newValue !== this.value) {this.value = newValue;this.callback(newValue);}}
}
Vue3
Vue3不再強調 MVVM,其底層是由 TypeScript 編寫的,將 Object.defineProperty 換成 Proxy 實現響應式系統,彌補了前者的缺陷,使用編譯器將模版換成高效的渲染函數,并通過虛擬 DOM 和調度系統實現高性能更新
/* 為什么要用 Reflect
統一為函數式寫法,符合面向對象
報錯不會中斷,而是返回 false(比如只讀的時候)
通過 receiver 參數可以指定 this上下文
*/const bucket = new WeakMap(); // 用于依賴收集function Vue3Reactive(obj) {return new Proxy(obj, {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;}});
}// 收集依賴函數
function track(target, key) {if (!activeEffect) return; // 如果沒有激活的 effect,直接返回let depsMap = bucket.get(target); // 獲取 target 的依賴映射if (!depsMap) bucket.set(target, (depsMap = new Map()));let deps = depsMap.get(key); // 獲取 key 的依賴集合if (!deps) depsMap.set(key, (deps = new Set())); deps.add(activeEffect); // 將當前激活的 effect 添加到依賴集合
}// 觸發更新函數
function trigger(target, key) {const depsMap = bucket.get(target); // 獲取 target 的依賴映射if (!depsMap) return; // 如果沒有依賴,直接返回const deps = depsMap.get(key); // 獲取 key 的依賴集合if (deps) deps.forEach(fn => fn()); // 遍歷依賴集合,執行每個依賴的函數
}// 示例
let activeEffect;
function effect(fn) {activeEffect = fn;fn(); // 立即執行一次activeEffect = null;
}const state = Vue3Reactive({ count: 0 });effect(() => {console.log('count is:', state.count); // 依賴收集
});state.count++; // 觸發更新
實際上,Vue3在底層上還做了許多細節的優化,比如惰性代理節省操作、微任務批次處理節流更新、自動對依賴進行清理等等