Historical archive
Vue 2 vs. Vue 3 Reactivity: defineProperty, Proxy, and Reflect
How Object.defineProperty powers basic interception, why Vue 2 had reactivity limitations, and how Proxy and Reflect support Vue 3's object-level reactivity.
Object.defineProperty()
Object.defineProperty(obj, prop, descriptor) defines one property on an object:
obj: the object to modifyprop: the property namedescriptor: configuration for the property
Object.defineProperties() defines several properties at once.
Data descriptors
A property descriptor can contain:
valuewritable, which defaults tofalseenumerable, which defaults tofalseconfigurable, which defaults tofalse
function createObject() {
var object = {}
Object.defineProperties(object, {
a: {
value: 1,
writable: true,
enumerable: true,
configurable: true,
},
b: {
value: 2,
},
})
return object
}
var object = createObject()
object.a = 5
object.b = 5
console.log(object) // { a: 5, b: 2 }
for (var key in object) {
console.log(key + ':' + object[key])
// Only a is enumerable.
}
delete object.a
delete object.b
console.log(object) // { b: 2 }
Intercept access with getters and setters
A descriptor cannot combine value or writable with get or set.
function createObject() {
var object = {}
var a = 1
Object.defineProperties(object, {
a: {
get() {
return `"a"'s value is ${a}.`
},
set(newValue) {
a = newValue
console.log(`a was assigned ${newValue}`)
},
},
b: {},
})
return object
}
var object = createObject()
console.log(object.a)
object.a = 2
Record assigned values
function DataArray() {
var value = null
var history = []
Object.defineProperty(this, 'value', {
get: function () {
return value
},
set: function (newValue) {
value = newValue
history.push({ value })
console.log(`A new value "${value}" was added`)
},
})
this.getHistory = function () {
return history
}
}
var data = new DataArray()
data.value = 123
data.value = 234
console.log(data.getHistory())
A minimal Vue 2-style binding
This simplified example updates a paragraph whenever the intercepted property changes:
<p>0</p>
<script>
function createObject() {
var object = {}
var a = 1
Object.defineProperty(object, 'a', {
get() {
return a
},
set(newValue) {
a = newValue
document.getElementsByTagName('p')[0].innerHTML = a
},
})
return object
}
var object = createObject()
object.a = 2
</script>
Vue 2 built reactivity around property-level getter and setter interception. Because interception was attached per property, adding properties and observing certain array operations required special handling.
Internal object operations
JavaScript objects support a set of internal operations reflected by familiar APIs:
var object = { a: 1, b: 2 }
// Prototype operations
Object.getPrototypeOf(object)
Object.setPrototypeOf(object, { c: 3 })
// Extensibility and integrity
Object.isExtensible(object)
Object.preventExtensions(object)
Object.seal(object)
Object.freeze(object)
// Own properties
Object.getOwnPropertyNames(object)
Object.defineProperty(object, 'c', { value: 3 })
object.hasOwnProperty('a')
Object.keys(object)
// Get, set, delete, and enumerate
console.log(object.a)
object.a = 3
delete object.a
for (var key in object) {
console.log(object[key])
}
// Function calls and construction are also internal operations.
function Test() {}
Test.call(null)
new Test()
These operations matter because a Proxy can intercept many of them.
Proxy
Object.defineProperty() configures individual properties. An ES2015 Proxy wraps a target and can intercept operations across the target object.
const proxy = new Proxy(target, handler)
targetis the object being wrapped.handlercontains traps for operations such as reading, assigning, enumerating, calling, or constructing.
A Proxy can wrap objects, arrays, and functions. This broader interception model is the foundation of Vue 3’s reactivity system.
Reflect
Reflect is a global object containing function-based versions of many internal object operations:
const value = Reflect.get(target, property)
const succeeded = Reflect.set(target, property, nextValue)
if (succeeded) {
console.log('Set successfully')
}
Proxy traps commonly delegate the default operation to Reflect, then add tracking or notification behavior around it.