How does Vue listen for array changes

Posted by ym_chaitu on Thu, 03 Mar 2022 13:21:43 +0100

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!

Topics: Javascript Vue