Vue 的雙向數據綁定是通過 數據劫持 + 發布-訂閱模式 實現的,具體分為以下三個關鍵機制:
1. 數據劫持(響應式系統)
Vue 使用 Object.defineProperty(Vue 2)或 Proxy(Vue 3)監聽數據變化。
Vue 2 實現:
// 簡化版數據劫持
function defineReactive(obj, key) {let value = obj[key];const dep = new Dep(); // 依賴收集器Object.defineProperty(obj, key, {get() {if (Dep.target) {dep.addSub(Dep.target); // 收集依賴(Watcher)}return value;},set(newVal) {if (newVal === value) return;value = newVal;dep.notify(); // 通知所有訂閱者更新}});
}
Vue 3 改進:
// 使用Proxy實現
const reactive = (target) => {return new Proxy(target, {get(target, key, receiver) {track(target, key); // 依賴收集return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {Reflect.set(target, key, value, receiver);trigger(target, key); // 觸發更新return true;}});
};
2. 依賴收集(發布-訂閱模式)
Dep 類:管理依賴(一個屬性對應一個Dep)Watcher 類:訂閱數據變化,觸發更新
class Dep {constructor() {this.subs = [];}addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => sub.update());}
}class Watcher {update() {// 執行更新(如重新渲染組件)}
}
3. 模板編譯(指令解析)
Vue 編譯器將模板中的指令(如 v-model)轉換為綁定代碼:
<input v-model="message">
編譯后相當于:
<input :value="message" @input="message = $event.target.value"
>
雙向綁定工作流程
初始化階段:遍歷 data 對象,用 Object.defineProperty/Proxy 劫持所有屬性編譯模板,為每個綁定創建 Watcher數據訪問時(觸發 getter):當前 Watcher 被添加到屬性的 Dep 中數據修改時(觸發 setter):Dep 通知所有 Watcher 更新Watcher 觸發組件重新渲染
4、Vue 2 vs Vue 3 實現對比
特性 | Vue 2 | Vue 3 |
---|---|---|
核心API | Object.defineProperty | Proxy |
監聽范圍 | 僅能劫持已定義屬性 | 動態新增屬性自動響應 |
數組處理 | 需重寫數組方法 | 直接監聽數組變化 |
性能 | 遞歸遍歷所有屬性 | 惰性監聽,按需觸發 |
5、常見問題解答
Q1:為什么 Vue 3 改用 Proxy?
解決 Vue 2 無法檢測對象/數組新增屬性的問題性能更好(無需遞歸初始化所有屬性)
Q2:v-model 在自定義組件中的原理?
<CustomComponent v-model="value" />
等價于:
<CustomComponent :modelValue="value"@update:modelValue="newVal => value = newVal"
/>
手寫極簡雙向綁定
// 簡易版Vue響應式
class MiniVue {constructor(options) {this.$data = options.data;this.observe(this.$data);this.compile(options.el);}observe(data) {Object.keys(data).forEach(key => {let value = data[key];const dep = new Dep();Object.defineProperty(data, key, {get() {Dep.target && dep.addSub(Dep.target);return value;},set(newVal) {value = newVal;dep.notify();}});});}compile(el) {const element = document.querySelector(el);this.walk(element);}walk(node) {if (node.nodeType === 1) { // 元素節點Array.from(node.attributes).forEach(attr => {if (attr.name.startsWith('v-')) {const dir = attr.name.substring(2);if (dir === 'model') {const key = attr.value;node.value = this.$data[key];new Watcher(this.$data, key, val => {node.value = val;});node.addEventListener('input', e => {this.$data[key] = e.target.value;});}}});}// 遞歸子節點...}
}
通過這種機制,Vue 實現了數據變化自動更新視圖、視圖輸入自動更新數據的雙向綁定效果。