[Hard] π Two-way Data Binding
1. Please explain the underlying principle of how Vue2 and Vue3 each implement two-way bindingβ
Please explain the underlying mechanism of two-way binding in Vue2 and Vue3.
To understand Vue two-way binding, you need to understand how the reactivity system works and how Vue2 and Vue3 implement it differently.
Vue2 implementationβ
Vue2 uses Object.defineProperty to implement reactivity. It wraps object properties with getter and setter, so reads/writes can be tracked.
1. Data hijackingβ
When component data is initialized in Vue2, Vue traverses each property and converts it into getter/setter via Object.defineProperty, so reads and writes can be observed.
2. Dependency collectionβ
When a render function executes and reads reactive properties, the getter runs. Vue records dependencies so affected components can be notified later.
3. Dispatching updatesβ
When data changes, the setter runs. Vue notifies dependent watchers/components and triggers re-render to update DOM.
Vue2 exampleβ
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); // triggers getter
data.name = 'Vue2 Reactivity'; // triggers setter
Vue2 limitationsβ
Object.defineProperty has several limitations:
- Cannot detect property addition/deletion without
Vue.set()/Vue.delete() - Cannot detect direct array index mutation reliably without patched array methods
- Performance overhead from deep traversal and upfront getter/setter definition
Vue3 implementationβ
Vue3 uses ES6 Proxy, which provides broader interception capability and better performance characteristics.
1. Proxy-based data hijackingβ
Vue3 wraps whole objects with new Proxy instead of defining getter/setter per key. This allows interception of more operations, including property add/delete.
2. More efficient dependency trackingβ
Vue3 no longer needs upfront getter/setter definition for every property. Proxy traps can handle operations dynamically (e.g., get, set, has, deleteProperty, etc.).
3. More precise update triggeringβ
Vue3 can track dependencies more precisely and reduce unnecessary re-renders.
Vue3 exampleβ
function reactive(target) {
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
console.log(`get ${key}: ${result}`);
return result;
},
set(target, key, value, receiver) {
const success = Reflect.set(target, key, value, receiver);
console.log(`set ${key}: ${value}`);
return success;
},
};
return new Proxy(target, handler);
}
const data = reactive({ name: 'Vue 3' });
console.log(data.name);
data.name = 'Vue 3 Reactivity';
console.log(data.name);
Vue2 vs Vue3 comparisonβ
| Feature | Vue2 | Vue3 |
|---|---|---|
| Core mechanism | Object.defineProperty | Proxy |
| Detect new properties | β requires Vue.set() | β native support |
| Detect property deletion | β requires Vue.delete() | β native support |
| Detect array index assignment | β limited | β native support |
| Performance | upfront deep walk | lazy + more efficient |
| Browser support | IE9+ | no IE11 support |
Conclusionβ
Vue2 uses Object.defineProperty, which works but has known limitations.
Vue3 uses Proxy, giving a more complete and flexible reactivity system with better performance.
2. Why does Vue3 use Proxy instead of Object.defineProperty?β
Why did Vue3 choose
ProxyoverObject.defineProperty?
Main reasonsβ
1. Stronger interception capabilityβ
Proxy can intercept many operations, while Object.defineProperty only covers property get/set.
// Operations Proxy can intercept
const handler = {
get() {},
set() {},
has() {},
deleteProperty() {},
ownKeys() {},
getOwnPropertyDescriptor() {},
defineProperty() {},
preventExtensions() {},
getPrototypeOf() {},
isExtensible() {},
setPrototypeOf() {},
apply() {},
construct() {},
};
2. Native array index trackingβ
// Vue2 limitation
const arr = [1, 2, 3];
arr[0] = 10; // often not tracked in Vue2-style defineProperty model
// Vue3
const arr2 = reactive([1, 2, 3]);
arr2[0] = 10; // tracked
3. Native support for dynamic property add/deleteβ
// Vue2 needs special API
Vue.set(obj, 'newKey', 'value');
// Vue3 native
const obj = reactive({});
obj.newKey = 'value';
delete obj.newKey;
4. Better performance modelβ
// Vue2: deep traversal + defineReactive for each key
function observe(obj) {
Object.keys(obj).forEach((key) => {
defineReactive(obj, key, obj[key]);
if (typeof obj[key] === 'object') {
observe(obj[key]);
}
});
}
// Vue3: proxy wrapper
function reactive(obj) {
return new Proxy(obj, handler);
}
5. Simpler internal implementationβ
Vue3 reactivity core is cleaner and easier to maintain.
Why Vue2 did not use Proxyβ
Mainly browser compatibility:
- Vue2 (released in 2016) needed broad support including IE
Proxycannot be fully polyfilled- Vue3 dropped IE11 support, so Proxy became viable
Practical comparisonβ
// ===== Vue2 limitations =====
const vm = new Vue({
data: {
obj: { a: 1 },
arr: [1, 2, 3],
},
});
// not reliably reactive in Vue2 without special APIs
vm.obj.b = 2;
delete vm.obj.a;
vm.arr[0] = 10;
vm.arr.length = 0;
// Vue2 workaround
Vue.set(vm.obj, 'b', 2);
Vue.delete(vm.obj, 'a');
vm.arr.splice(0, 1, 10);
// ===== Vue3 native support =====
const state = reactive({
obj: { a: 1 },
arr: [1, 2, 3],
});
state.obj.b = 2;
delete state.obj.a;
state.arr[0] = 10;
state.arr.length = 0;
Summaryβ
Vue3 uses Proxy to:
- β Provide complete reactivity coverage (add/delete/index changes)
- β Improve performance (less upfront traversal)
- β Simplify implementation
- β Improve developer experience (fewer special APIs)
Tradeoff: no support for legacy IE11, which is acceptable for modern web targets.