How does Vue listen for array changes
We know through object DefineProperty () hijacking an array to set getter and setter, the push, splice, and pop methods of the array to be invoked will not trigger the setter of the array when changing the array elements. This will result in the change of the array after using the above method, and the changes can not be reflected on the page in time. That is, the array data change is not responsive (for those who do not understand the above, please refer to this article). But in the actual development with vue, for the responsive array, when using push, splice, pop and other methods to change the array, the page will reflect this change in time. So how is it realized in vue?
From the vue source code, we can see that vue rewrites the push, splice, pop and other methods of array.
1 // src/core/observer/array.js 2 3 // Gets the prototype array of the array Prototype, which has our commonly used array methods 4 const arrayProto = Array.prototype 5 // Create an empty object arrayMethods and point the prototype of arrayMethods to array prototype 6 export const arrayMethods = Object.create(arrayProto) 7 8 // Lists the array method names that need to be overridden 9 const methodsToPatch = [ 10 'push', 11 'pop', 12 'shift', 13 'unshift', 14 'splice', 15 'sort', 16 'reverse' 17 ] 18 // Traverse the above array method names and add the above rewritten array methods to the arrayMethods object in turn 19 methodsToPatch.forEach(function (method) { 20 // Save a copy of the original array method corresponding to the current method name 21 const original = arrayProto[method] 22 // Define the rewritten method on the arrayMethods object. function mutator() {} is the rewritten method 23 def(arrayMethods, method, function mutator (...args) { 24 // Call the original array method, pass in the parameter args, and assign the execution result to result 25 const result = original.apply(this, args) 26 // When the array calls the rewritten method, this points to the array. When the array is a response, you can get its__ ob__ attribute 27 const ob = this.__ob__ 28 let inserted 29 switch (method) { 30 case 'push': 31 case 'unshift': 32 inserted = args 33 break 34 case 'splice': 35 inserted = args.slice(2) 36 break 37 } 38 if (inserted) ob.observeArray(inserted) 39 // Notifies its subscribers of changes to the current array 40 ob.dep.notify() 41 // Finally, the execution result is returned 42 return result 43 }) 44 })
As can be seen from the above, array JS rewrites the seven methods of push, pop, shift, unshift, splice, sort and reverse of the array. When the rewriting method is implemented, it not only calls the original method corresponding to the array method name once and returns the execution result, but also executes ob Dep.notify() notifies its subscribers of the changes of the current array, so that when the array is changed by using the rewritten method, the array subscribers will update the changes to the page.
In addition to the above seven methods of rewriting the completion group, we also need to apply these rewritten methods to the array. Therefore, in the Observer constructor, we can see that whether the data type is array will be judged when listening to data. When it is an array, if the browser supports__ proto__, The prototype of the current data__ proto__ Point to the rewritten array method object arrayMethods, if the browser does not support it__ proto__, The method rewritten on arrayMethods is directly defined on the current data object; When the data type is non array, continue to listen to the data recursively.
/ src/core/observer/index.js 2 export class Observer { 3 ... 4 constructor (value: any) { 5 this.value = value 6 this.dep = new Dep() 7 this.vmCount = 0 8 def(value, '__ob__', this) 9 if (Array.isArray(value)) { 10 if (hasProto) { 11 protoAugment(value, arrayMethods) 12 } else { 13 copyAugment(value, arrayMethods, arrayKeys) 14 } 15 this.observeArray(value) 16 } else { 17 this.walk(value) 18 } 19 } 20 ... 21 } 22 function protoAugment (target, src: Object) { 23 /* eslint-disable no-proto */ 24 target.__proto__ = src 25 /* eslint-enable no-proto */ 26 } 27 function copyAugment (target: Object, src: Object, keys: Array<string>) { 28 for (let i = 0, l = keys.length; i < l; i++) { 29 const key = keys[i] 30 def(target, key, src[key]) 31 } 32 }
After the above processing, for the array, when we call its method to process the array, we will obtain the array method according to the following prototype chain:
For responsive arrays, when the browser supports__ proto__ When using push and other methods, first look for the push method from its prototype arrayMethods, that is, the rewritten method. After processing, the change of the array will be notified to its subscribers and the page will be updated. When it is not queried on arrayMethods, it will go up in array Query on prototype; When the browser does not support__ proto__ When using push and other methods, you will first query from the array itself. If you can't find it, you will query upward and then array Query on prototype.
For non responsive arrays, when using push and other methods, they will be directly from array Query on prototype.
It is worth mentioning that the source code determines whether the browser supports it__ proto__ To apply the rewritten array method to the array using the protofragment and copyfragment methods respectively, because it is not supported for IE browsers of IE10 and below__ proto__ Properties:
The above screenshot refers to Vue source code analysis V - data response system
Conclusion:
After the array is processed into responsive data, if the original array method is used to change the array, the array value will change, but the setter of the array will not be triggered to notify all places that depend on the array to update. Therefore, vue monitors the array change by rewriting some methods of the array, The overridden method will be manually triggered to notify all dependencies of the array to be updated.
If my content can help you, I will be very happy!