Deep Dive into Vue.js Reactivity and Data Binding Implementation

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.

Posted on Sat, 05 Sep 2026 16:33:47 +0000 by kinadian