Read the guide
In VUe3.0, the responsive data section deprecates Object.defineProperty and uses Proxy instead. This article will analyze why VUE chose to discard Object.defineProperty in the following aspects.
Object.defineProperty
Is it really impossible to detect array subscript changes?- Analyze the pair array in vue2. X
Observe
Part of the source - contrast
Object.defineProperty
和Proxy
Can’t monitor array index change?
I’ve seen this statement on some tech blogs that Object. DefineProperty has a bug that doesn’t listen for array changes:
The change of array subscripts cannot be monitored. As a result, the value of the array is directly set by the array subscripts and cannot respond in real time. That’s why Vue has set up hack methods for 7 variational arrays (push, POP, Shift, unshift, splice, sort, reverse) to solve the problem.
The first defect of Object.defineProperty is the inability to listen for array changes. Vue documentation, however, states that Vue can detect array changes, but only the following eight methods are not detectable: vm.items[indexOfItem] = newValue.
This is problematic, in fact object.defineProperty itself can detect array subscript changes, but in the implementation of Vue, this feature is dropped for performance/experience value reasons.
Let’s use an example to justify Object.defineProperty:
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
enumerable: true.configurable: true.get: function defineGet() {
console.log(`get key: ${key} value: ${value}`)
return value
},
set: function defineSet(newVal) {
console.log(`set key: ${key} value: ${newVal}`)
value = newVal
}
})
}
function observe(data) {
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key])
})
}
let arr = [1.2.3]
observe(arr)
Copy the code
The above code jails each property of the array ARR via Object.defineProperty. Now let’s manipulate the array ARR to see which actions trigger the getter and setter methods of the array.
1. Obtain an element by subscript and modify the value of an element
getter
setter
Next, let’s try some of the array operations to see if they fire.
2. Array push method
Push does not trigger setter and getter methods. The subscript of array can be regarded as the key in the object. Here, after push, the element with index 3 is added, but the new subscript is not observed, so it will not trigger.
3. Array unshift method
Oh, my god. What happened?
The unshift operation will cause the index 0,1,2,3 to change. This will require the index 0,1,2,3 to be fetched and reassigned, so the evaluation triggers the getter, and the assignment triggers the setter.
Here we try to get the corresponding element by index:
Only properties with indexes 0, 1, and 2 trigger getters.
Here, we can compare object, array arr initial values for [1, 2, 3], the index is 0, the execution of the only observe method, so no matter how to change the length of the array, later still only index for 0 elements change would trigger, other new indexes, It is equivalent to a new attribute in the object. You need to manually observe it.
4. Array pop method
The getter is triggered when the element removed is an element with reference 2.
The setter and getter are no longer fired when the element with index 2 is deleted and its value is modified or retrieved.
This is the same with the object. If the index of an array is deleted, it is the same as if the property of the object is deleted.
At this point, we can simply sum up a conclusion.
Object.defineproperty behaves the same in an array as it does in an Object. The index of the array is considered to be the key in the Object.
- Can be triggered when the value of the corresponding element is accessed or set through the index
getter
和setter
methods - through
push
或unshift
The index is added. For newly added attributes, manual initialization is requiredobserve
. - through
pop
或shift
Deleting elements, which deletes and updates indexes, also firessetter
和getter
Methods.
So object.defineProperty has the ability to monitor array subscript changes, but vue2. X drops this feature.
What does vue do with array Observe?
Vue’s Observer class is defined in core/ Observer /index.js.
As you can see, vue’s Observer does separate processing for arrays.
HasProto determines whether an instance of an array has a __proto__ attribute. If it has a __proto__ attribute, the protoAugment method is executed, overwriting arrayMethods to the prototype. HasProto is defined as follows.
ArrayMethods is a rewrite of array methods, defined in core/ Observer /array.js.
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype * /
import { def } from '.. /util/index'
// Copy the Array constructor prototype. Array.prototype is also an Array.
const arrayProto = Array.prototype
// Create an object whose __proto__ points to arrayProto, so arrayMethods' __proto__ contains all methods of the array.
export const arrayMethods = Object.create(arrayProto)
// The following array is the method to override
const methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
/** * Intercept mutating methods and emit events */
// Iterate over the methodsToPatch array, overriding the methods in it
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
// The def method is defined in the lang.js file, which redefines the attribute via Object.defineProperty.
// Find the method we want to override in arrayMethods and redefine it
def(arrayMethods, method, function mutator (. args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
// For push, unshift adds an index, so manually observe
case 'push':
case 'unshift':
inserted = args
break
// if a third parameter is passed to the splice method, the index will also be added, so you need to manually observe
case 'splice':
inserted = args.slice(2)
break
}
ObserveArray (observeArray); // observeArray (observeArray); // observeArray (observeArray); // observeArray (observeArray); // observeArray (observeArray)
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
Copy the code
Three Object. DefineProperty VS the Proxy
We already know that Object.defineProperty behaves identically for arrays and objects, so what are the advantages and disadvantages of object.defineProperty compared to Proxy?
Object.defineproperty can only hijack attributes of an Object, whereas Proxy is a direct Proxy Object.
Because Object.defineProperty can only hijack attributes, each attribute of the Object needs to be traversed. Proxy can directly Proxy objects.
2. Object.defineProperty Manually Observe the added attribute.
Object.defineproperty hijacks the attributes of the Object, so when adding attributes, you need to traverse the Object again and then use Object.defineProperty to hijack the new attributes.
For this reason, when using Vue to add new attributes to arrays or objects in Data, you need to use vm.$set to ensure that the new attributes are also responsive.
The set method is defined in core/ Observer /index.js, and the core code is shown below.
/** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */
export function set (target: Array<any> | Object, key: any, val: any) :any {
// If target is an array and key is a valid array index, the array splice method is called,
// As we said above, the array splice method will be overridden, and the overridden method will manually Observe
// So vue's set method, for arrays, simply calls overwrite splice
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// For objects, if the key is an attribute in the object, the update can be triggered by changing the value directly
if (key intarget && ! (keyin Object.prototype)) {
target[key] = val
return val
}
// All vue responsive objects have an __ob__ attribute added to them, so we can determine whether they are responsive objects based on the __ob__ attribute
const ob = (target: any).__ob__
// If it is not a reactive object, assign it directly
if(! ob) { target[key] = valreturn val
}
// Call defineReactive adds getters and setters to data,
// So vue's set method, for reactive objects, calls defineReactive to redefine reactive objects
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
Copy the code
The set method treats target as an array and an object separately. When target is an array, the overwritten splice method is invoked to manually Observe.
For objects, if the key is a property of the object, change the value directly to trigger the update, otherwise call the defineReactive method to redefine the reactive object.
If the proxy is implemented, the proxy intercepts the setting of object attributes through set(Target, propKey, value, receiver), which can intercept the new attributes of the object.
Not only that, Proxy methods on arrays can also be detected, without the need to hack as in the above vue2. X source code.
Perfect!!
3. Proxy supports 13 interception operations, which defineProperty does not
-
Get (target, propKey, receiver) : Intercepts reading of object properties, such as proxy.foo and proxy[‘foo’].
-
Set (target, propKey, value, receiver) : Intercepts the setting of object properties, such as proxy.foo = v or proxy[‘foo’] = v, and returns a Boolean value.
-
Has (target, propKey) : Intercepts the propKey in proxy operation and returns a Boolean value.
-
DeleteProperty (target, propKey) : Intercepts the operation of delete Proxy [propKey] and returns a Boolean value.
-
OwnKeys (target) : interception Object. GetOwnPropertyNames (proxy), Object. GetOwnPropertySymbols (proxy), the Object. The keys (proxy), for… The in loop returns an array. This method returns the property names of all of the target Object’s own properties, whereas object.keys () returns only the traversable properties of the target Object itself.
-
GetOwnPropertyDescriptor (target, propKey) : interception Object. GetOwnPropertyDescriptor (proxy, propKey), returns the attributes describe objects.
-
DefineProperty (target, propKey propDesc) : Intercepts Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDescs), and returns a Boolean value.
-
PreventExtensions (target) : Intercepts Object.preventExtensions(proxy), returns a Boolean.
-
GetPrototypeOf (target) : Intercepts object.getProtoTypeof (proxy) and returns an Object.
-
IsExtensible (Target) : Intercepts Object. IsExtensible (proxy), returning a Boolean value.
-
SetPrototypeOf (target, proto) : Intercepts Object.setPrototypeOf(proxy, proto) and returns a Boolean value. If the target object is a function, there are two additional operations that can be intercepted.
-
Apply (target, object, args) : intercepts operations called by Proxy instances as functions, such as Proxy (… The args), proxy. Call (object,… The args), proxy. Apply (…). .
-
Construct (target, args) : intercepts operations called by Proxy instances as constructors, such as new Proxy (… The args).
4. New standard performance bonus
Proxy is the new standard. In the long run, JS engines will continue to optimize Proxy, but getters and setters will basically no longer be optimized.
5. Proxy compatibility is poor
As you can see, Proxy is a disaster for Internet Explorer.
In addition, there is no Polyfill scheme that fully supports all interception methods of Proxy. There is a Proxy-Polyfill written by Google that only supports four interception methods of GET, set, apply and Construct. Supports IE9+ and Safari 6+.
Four summarizes
-
Object.defineproperty behaves the same for arrays and objects. It is not that we cannot monitor array subscript changes. The failure to automatically update reactive data through array indexes in Vue2.
-
Object. DefineProperty and Proxy are essentially different. DefineProperty can only hijack attributes, and new attributes need to be manually observed.
-
Proxy as a new standard, browser manufacturers will continue to optimize it, but its compatibility is also a problem, and there is no complete polyfill solution at present.
reference
Developer.mozilla.org/zh-CN/docs/…
Segmentfault.com/a/119000001…
zhuanlan.zhihu.com/p/35080324
es6.ruanyifeng.com/#docs/proxy
Welcome to my public account “front-end xiaoyuan”, I will update the original articles on it regularly.