Too late to explain, get in the car……
In the previous article, the vue2. X responsivity principle was mainly about object responsivity. Today, I will supplement the array responsivity principle, because Vue treats arrays specially.
Why doesn’t Vue treat arrays with Object.defineProperty like objects? Is Object.defineProperty unable to detect arrays? Or is it something else? So how does it listen on an array? With these questions, let’s find out…
Does Object.defineProperty support arrays
First let’s do a test to see if Object.defineProperty supports arrays.
Function defineReactive(obj, key, val) {Object. True, the get () {the console. The log (' 🐯 -- -- -- -- -- read ', val) return val. {}, the set (newVal) if (val = = = newVal) return val = newVal console. The log (' 🐯 change -- -- -- -- -- -- -- -- ', val, obj)}})}Copy the code
Again, the defineReactive function is used to go through the array, using the index of the array as the key to give each entry a getter/setter.
Let array = [1,2,3,4,5] array.foreach ((c) => {defineReactive(array, index, c)})Copy the code
If we print it on the console, we can see the result of the print, which means that the array item is called with a getter/setter. Back to the problem, Object.defineProperty can listen on arrays, it supports arrays.
Why doesn’t Vue provide listening for array properties
The second question is, if Object.defineProperty has this capability, why doesn’t Vue use it to listen for array properties
Someone mentioned the length attribute. Changing the length attribute can result in empty elements. Indeed, Object.defineProperty can’t handle these empty arrays. But if you change the length property to increase the length of the array, you increase the length of the array, which is also invisible to vUE in the object.
In the second log above, when assigning a value to an item in the array, the setter is triggered, and when the setter is triggered, the data update calls the getter again, which affects performance. Also, a lot of times we don’t know how long the array is, so we can’t get getters and setters in advance, and if the array is too long it can cause performance problems, and in Judah’s own words the performance costs are not proportional to the user experience gains.
Post one of Judah’s answers on Github.
🙈 : If you know the length of the array, you can theoretically set getters/setters for all indexes beforehand. But a lot of times you don’t know the length of the array, and b, if it’s a big array, adding getter/setter beforehand is a bit of a performance burden.
The bottom line is that vUE could theoretically do this, but for performance reasons it doesn’t. Instead, it uses an array variation method to trigger view updates.
How does VUE listen on arrays
Having solved the above two problems, let’s take a look at how vUE array variation is implemented.
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
Copy the code
ObserveArray turns the object data in the array into detectable, responsive data. The observeArray and Walk functions are still the focus of our previous article on the vue2. X responsive principle. Vue has made a special treatment of the array here, which relies on the two functions of protoAugment and copyAugment.
export const hasProto = '__proto__' in {}
Copy the code
First hasProto determines whether the browser supports the __proto__ attribute to decide whether to perform protoAugment or copyAugment. Before we look at these two functions, let’s take a look at array.js, the core file of array variation, and also know what arrayMethods and arrayKeys are.
// array.js import { def } from '.. /util/index' const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) /** * Intercept mutating methods and emit events */ ; [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] .forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (... Const result = original.apply(this, [ob__] {case 'push': case 'unshift': {case 'push': case 'unshift': inserted = args break case 'splice': Insert = args.slice(2) break} // INSERT Indicates data is inserted. Observe if (inserted) ob.observearray (inserted) // notify change ob.dep.notify() return result }) })Copy the code
/** * Define a property. */ export function def (obj: Object, key: string, val:any,enumerable? :boolean) { Object.defineProperty(obj, key, { value: val, enumerable: !! enumerable, writable: true, configurable: true }) }Copy the code
Vue makes arrayMethods inherit the array prototype through Object.create. Object. Create is essentially primitive inheritance, and the array prototype is attached to the arrayMethods prototype chain.
The def function mainly defines attributes. By iterating through the seven methods, we override the seven methods on the arrayMethods prototype chain with the def function, call the original methods of the array internally, and finally update the view with notify. Inserted: New data is inserted, and obsserve is required for the new data.
Let’s go back to the protoAugment function and copyAugment these two functions.
/**
* Augment an target Object or Array by intercepting
* the prototype chain using __proto__
*/
function protoAugment (target, src: Object, keys: any) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
/**
* Augment an target Object or Array by defining
* hidden properties.
*/
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
Copy the code
ProtoAugment makes the array’s prototype chain point to arrayMethods. When __proto__ is not supported by the browser, we iterate over the arrayKeys, using def to manually mount the methods on arrayMethods to the target data, equivalent to a polyfill. The only difference is that protoAugment attaches arrayMethods to the target’s prototype chain, while copyAugment defines arrayMethods directly to the target’s properties.
summary
For the sake of performance, Vue does not use Object.defineProperty to listen on the array. Instead, it mutate the seven commonly used methods by overriding the prototype of the array.
The source address