[Hard] 📄 Two-way Data Binding
1. Please explain the underlying principle of how Vue2 and Vue3 each implement two-way binding
請解釋 Vue2 和 Vue3 各自如何實現雙向綁定的底層原理?
要理解 Vue 的雙向綁定,需要先明白響應式系統的運作機制,以及 Vue2 與 Vue3 在實作上的差異。
Vue2 的實作方式
Vue2 使用 Object.defineProperty 來實現雙向綁定,這個方法可以將一個物件的屬性包裝成 getter 和 setter,並且可以監聽物件屬性的變化。流程如下:
1. Data Hijacking(資料劫持)
在 Vue2 中,當一個元件中某個資料的物件被建立時,Vue 會遍歷整個物件中的所有屬性,並使用 Object.defineProperty 將這些屬性轉換成 getter 和 setter,這才使 Vue 可以追蹤資料的讀取和修改。
2. Dependency Collection(依賴收集)
每當元件中的渲染函式被執行時,會讀取 data 中的屬性,這時候就會觸發 getter,Vue 會記錄這些依賴,確保當資料變化時,能夠通知到依賴這些資料的元件。
3. Dispatching Updates(派發更新)
當資料被修改時,會觸發 setter,這時候 Vue 會通知到所有依賴這些資料的元件,並且重新執行渲染函式,更新 DOM。
Vue2 程式碼範例
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log(`get ${key}: ${val}`);
return val;
},
set: function reactiveSetter(newVal) {
console.log(`set ${key}: ${newVal}`);
val = newVal;
},
});
}
const data = { name: 'Pitt' };
defineReactive(data, 'name', data.name);
console.log(data.name); // 觸發 getter,印出 "get name: Pitt"
data.name = 'Vue2 Reactivity'; // 觸發 setter,印出 "set name: Vue2 Reactivity"
Vue2 的限制
使用 Object.defineProperty 存在一些限制:
- 無法偵測物件屬性的新增或刪除:必須使用
Vue.set()或Vue.delete() - 無法偵測陣列索引的變化:必須使用 Vue 提供的陣列方法(如
push、pop等) - 效能問題:需要遞迴遍歷所有屬性,預先定義 getter 和 setter
Vue3 的實作方式
Vue3 引入了 ES6 的 Proxy,這個方法可以將一個物件包裝成一個代理,並且可以監聽物件屬性的變化,同時效能更加優化。流程如下:
1. 使用 Proxy 進行資料劫持
在 Vue3 中會使用 new Proxy 建立對資料的代理,而不再是逐一對資料的屬性進行定義 getter 和 setter,這樣除了可以針對更細緻的層面追蹤資料變化,同時也能攔截更多類型的操作,例如屬性的新增或刪除。
2. 更高效的依賴追蹤
使用 Proxy,Vue3 能夠更高效追蹤依賴,因為不再需要預先定義 getter / setter,而且 Proxy 的攔截能力更強,可以攔截多達 13 種操作(如 get、set、has、deleteProperty 等)。
3. 自動的最小化重新渲染
當資料變化時,Vue3 可以更精準地確定哪部分的 UI 需要進行更新,從而減少不必要的重新渲染,提升網頁效能。
Vue3 程式碼範例
function reactive(target) {
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
console.log(`獲取 ${key}: ${result}`);
return result;
},
set(target, key, value, receiver) {
const success = Reflect.set(target, key, value, receiver);
console.log(`設置 ${key}: ${value}`);
return success;
},
};
return new Proxy(target, handler);
}
const data = reactive({ name: 'Vue 3' });
console.log(data.name); // 讀取資料,觸發 Proxy 的 get,印出 "獲取 name: Vue 3"
data.name = 'Vue 3 Reactivity'; // 修改資料,觸發 Proxy 的 set,印出 "設置 name: Vue 3 Reactivity"
console.log(data.name); // 印出 "獲取 name: Vue 3 Reactivity"
Vue2 vs Vue3 比較表
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 實作方式 | Object.defineProperty | Proxy |
| 偵測新增屬性 | ❌ 需使用 Vue.set() | ✅ 原生支援 |
| 偵測屬性刪除 | ❌ 需使用 Vue.delete() | ✅ 原生支援 |
| 偵測陣列索引 | ❌ 需使用特定方法 | ✅ 原生支援 |
| 效能 | 需遞迴遍歷所有屬性 | 惰性處理,效能更好 |
| 瀏覽器支援 | IE9+ | 不支援 IE11 |
結論
Vue2 使用 Object.defineProperty 來實現雙向綁定,但這種方法存在一定的限制(比如無法偵測物件的屬性新增或刪除)。Vue3 引入了 ES6 的 Proxy,提供了更強大與靈活的響應式系統,同時也能提升效能。這是 Vue3 相較於 Vue2 的重大改進之一。
2. Why does Vue3 use Proxy instead of Object.defineProperty?
為什麼 Vue3 要使用
Proxy而不是Object.defineProperty?