Before diving into the code, it is important to understand the Model-View-ViewModel (MVVM) architecture that inspires Vue. Additionally, familiarity with specific ES6 features is crucial: Object.defineProperty for legacy implementations, and Proxy combined with Reflect for modern reactivity.
Vue 2 Reactivity Implementation
Vue 2 utilizes Object.defineProperty to perform data hijacking. This method allows defining getters and setters for specific properties, enabling the framework to track reads and writes. The following example demonstrates a simplified version of this mechanism:
let internalValue = 10;
const state = {
name: 'Alice'
};
Object.defineProperty(state, 'counter', {
enumerable: true,
configurable: true,
get() {
console.log('Accessing counter property');
return internalValue;
},
set(newValue) {
console.log(`Modifying counter property to: ${newValue}`);
if (newValue !== internalValue) {
internalValue = newValue;
}
}
});
console.log(state.counter); // Triggers get
state.counter = 20; // Triggers set
This approach creates a dependency tracking system where the Vue instance (vm) proxies the data object properties. However, this method has inherent limitations: adding or deleting properties dynamically will not trigger updates, and modifying array elements directly via index will not be detected.
Vue 3 Reactivity Implementation
Vue 3 addresses these limitations by leveraging ES6 Proxy. Unlike Object.defineProperty, which must define specific properties, a Proxy can intercept all operations on the target object. This includes property reading, writing, addition, and deletion.
const targetData = {
username: 'Bob',
role: 'Admin'
};
// Simplified Vue 3 reactive handler
const handler = {
get(target, property) {
console.log(`Trapping GET on property: ${property}`);
return Reflect.get(target, property);
},
set(target, property, value) {
console.log(`Trapping SET on property: ${property}`);
return Reflect.set(target, property, value);
},
deleteProperty(target, property) {
console.log(`Trapping DELETE on property: ${property}`);
return Reflect.deleteProperty(target, property);
}
};
const reactiveProxy = new Proxy(targetData, handler);
console.log(reactiveProxy.username);
reactiveProxy.active = true;
delete reactiveProxy.role;
The Reflect API is used within the traps to perform the default operations on the target object. This combination provides a more robust and performant reactivity system that handles dynamic data structures and array mutations naturally without the specific caveats found in the previous version.