Vue 2 maps properties from
dataonto the instance through data proxying, then usesObject.definePropertyfor observation to complete the reactive loop of “data change → view update.” The core goals are to simplify access paths and automatically refresh the UI. Keywords: Vue 2, Object.defineProperty, reactivity.
The technical snapshot outlines the core implementation details
| Parameter | Description |
|---|---|
| Core topic | Vue 2 data proxying and data observation |
| Primary language | JavaScript |
| Core mechanisms | Object.defineProperty, getter/setter |
| Reactive targets | Plain objects, array mutation methods |
| Typical scenarios | Accessing this.name from data, data changes driving view updates |
| Star count | Not provided in the source material |
| Core dependencies | Vue 2 runtime, Dep/Watcher model |
Data proxying moves the access entry point onto the Vue instance
You can think of data proxying like this: the real data still lives inside an internal object, but developers do not need to go through a long property path. Instead, they can read it directly from the instance. In Vue 2, the common experience is that you can write this.name instead of this._data.name.
This design solves two problems. First, it shortens access in templates and application code. Second, it allows the framework to intercept reads and writes at the instance layer, which prepares the ground for reactive tracking.
Object.defineProperty implements the proxy layer
const data = { name: '张三', age: 18 } // Original data
const vm = { _data: data } // Internal storage
Object.defineProperty(vm, 'name', {
get() {
return vm._data.name // Forward reads to _data
},
set(newVal) {
vm._data.name = newVal // Sync writes to the underlying data
}
})
console.log(vm.name) // Output: 张三
vm.name = '李四'
console.log(vm._data.name) // Output: 李四
This code demonstrates the smallest possible data proxy: the instance property does not store the value itself. It only forwards reads and writes to the internal _data object.
The relationship between data and _data separates configuration from storage
In Vue 2, data is closer to the raw data configuration that the developer passes into the framework. _data is the internal object Vue takes over for actual storage and reactive processing. In practice, developers usually access data through instance proxies instead of manipulating the internal structure directly.
Many references mention vm.$data and vm._data. You can treat the former as the public access entry point, while the latter is more of an internal implementation detail. They both point to the same underlying data, but they differ in semantics and intended usage boundaries.
Verifying the proxy relationship in the console is the most direct approach
console.log(vm._data.name === vm.name) // true
console.log(vm.$data === vm._data) // In Vue 2, they usually reference the same underlying data
These checks show that the instance property is just a facade. The real data still lives in the reactive storage layer.
Data observation turns property changes into view update signals
If data proxying answers “how do we access data conveniently,” then data observation answers “how does the page know when to update after a change.” Vue 2 solves this by defining a getter and setter for each property.
The getter records who depends on the value during reads. The setter notifies those dependencies to re-run during writes. As a result, template rendering, computed properties, and watcher logic can all update after the data changes.
The getter and setter form the minimal reactive loop
Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
dep.addSub(Dep.target) // Collect the current dependency
}
return val // Return the current value
},
set(newVal) {
if (newVal === val) return // Skip updates if the value has not changed
val = newVal
dep.notify() // Notify dependencies to re-render
}
})
This logic shows the core of Vue 2 reactivity: collect dependencies on read, and dispatch updates on write.
Array observation requires special handling because index changes do not naturally trigger setters
Object properties can be intercepted one by one, but arrays are not just a simple key-value update case. For example, push and splice do not trigger the setter of an existing property one by one. That means Vue 2 cannot rely on Object.defineProperty alone to make arrays reactive.
To solve this, Vue 2 patches seven methods that mutate the original array: push, pop, shift, unshift, splice, sort, and reverse. After performing the original operation, these methods also trigger dependency notifications.
Patched array mutation methods can drive view refreshes
new Vue({
data: {
list: [1, 2, 3]
},
mounted() {
this.list.push(4) // Call the enhanced push method to trigger an update
this.list.splice(1, 0, { name: 'new' }) // Newly inserted objects also continue through reactive processing
}
})
This example shows that Vue 2 does not observe every possible array behavior. Instead, it focuses on wrapping high-frequency mutation methods so they actively notify the view layer after changing data.
Understanding this mechanism explains the design boundaries of Vue 2
Vue 2’s approach had strong engineering value for its time: good compatibility, a clear implementation model, and enough power for most business scenarios. But it also has natural limits. Cases such as adding new object properties, assigning array items by index, or changing length are not handled in a fully uniform way.
That is also a key reason Vue 3 moved to Proxy. Proxy can intercept more operations at the whole-object level, which avoids Vue 2’s need to patch objects and arrays separately.
A practical mental model for the full flow
// Access phase: this.name
// 1. Hit the instance proxy
// 2. Forward to _data.name
// 3. Trigger the getter to collect dependencies
// Update phase: this.name = '新值'
// 1. Hit the instance proxy setter
// 2. Write to _data.name
// 3. Trigger the reactive setter
// 4. Notify the watcher to update the view
You can use this flow as the shortest path to understanding Vue 2’s reactivity chain.
The FAQ provides structured answers to common questions
1. Why does Vue 2 use data proxying instead of forcing this._data.name?
Because proxying shortens the access path and centralizes reads and writes at the instance layer. That improves developer experience and makes reactive interception easier for the framework.
2. Why does Vue 2 need to patch methods like push and splice?
Because array mutation operations do not naturally trigger setters the way plain object property assignments do. Vue 2 can only wrap these methods and manually notify dependencies after the mutation.
3. How should we understand data, $data, and _data?
data is the data configuration provided during development. _data is Vue’s internal object for actual storage and reactive processing. $data is the public access entry point to the core data. In day-to-day development, you should usually access state through instance proxy properties first.
Core Summary: This article reconstructs Vue 2’s data proxying and observation mechanisms, clarifies the relationship among data, _data, and instance proxy properties, breaks down how getter/setter pairs collect dependencies and trigger updates, and explains why array mutation methods can refresh the view.