This is the second day of my participation in the August More text Challenge. For details, see:August is more challenging

Vue change detection -Array

Last time we talked about detecting changes in Object data. The Object change detection relies on the Object prototype method object.defineProperty to listen for get and set methods to collect dependencies in get and notify updates in set. There is no Object. DefineProperty method for Array data, so we can’t listen like an Object.

1. How to detect changes in array data?

In fact, the mechanism of change detection remains the same, collecting dependencies when data is obtained, and notifying dependencies of updates when data changes.

2. How do you collect dependencies?

Again, you collect dependencies in get, so whoever gets this array, you collect dependencies in GET. This is the same as the dependency collection mechanism for objects.

3. How to detect changes in array data? (How do I notify dependency updates?)

Since there is no Object. DefineProperty method for arrays, you cannot use GET or set to monitor changes. But if the array changes, then the array must be manipulated, and the only ways JavaScript can manipulate an array are ‘push’,’pop’,’shift’,’unshift’,’splice’,’sort’,’reverse’.

Vue internally overrides the methods that operate on the array, calling the native array method in the overridden method, so that the overridden method has the same function as the native method. There are other things you can do in the overridden method, such as notifying dependency updates.

4. Array method interceptor

/ / SRC/core source position/observer/array. Js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto) // Create an Object as an interceptor. Object.create creates a new Object with __proto__ as the first argument passed
// arrayMethods is an array method interceptor
const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method] // Native methods
  def(arrayMethods, method, function mutator (. args) { // Add a method property to arrayMethods. The value of the property is function
    const result = original.apply(this, args)
    const ob = this.__ob__ / / the Observer class instance
    ob.dep.notify()
    return result
  })
})
Copy the code

5. How does the interceptor work?

Using this interceptor is as simple as setting the array instance’s __proto__ attribute to the interceptor, so that when the array instance calls a native method such as push, it will call push on arrayMethods, which is the mutator method we override.

How to mount __proto__

/ / the source code is located in the SRC/core/observer/index. Js
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number;

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    
    def(value, '__ob__'.this)
    if (Array.isArray(value)) {
      if (hasProto) { // Check whether __proto__ is supported by the browser
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value) // Convert all elements of the array into responsive data that can be monitored
    } else {
      this.walk(value)
    }
  }

/ * * * Object. GetOwnPropertyNames * return value: an array type: contains the specified Object of its own enumeration and an enumeration of an array of property names of * /
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
['push'.'splice'.'__proto__'.'length']

ArrayMethods = arrayMethods (arrayMethods)
 function protoAugment (target, src: Object) {
  target.__proto__ = src
} 

// If __protp__ is not supported by the browser, this method is called to loop the methods overridden in the interceptor into the array instance
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

6. Depth detection

In Vue, both Object data and Array data change detection are deep detection. In Vue, both Object data and Array data change detection are deep detection. The so-called deep detection is not only to detect the changes of the data itself, but also to detect the changes of all the sub-data in the data.

export class Observer {
  value: any;
  dep: Dep;

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    def(value, '__ob__'.this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)   // Convert all elements of the array into detectable responses
    } else {
      this.walk(value)
    }
  }


  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
Copy the code

7. Added array element detection

We can already detect existing elements in the array, but if a new element is pushed, that is, a new element is added, we should also make the new element detectable. This implementation is also simple, we just need to take the new data and call the Observe function to make it detectable.

There are three methods to add elements to an array: push, unshift, and splice. We can get the new data by listening to these three methods.

methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (. args) {
    const result = original.apply(this, args)
    const ob = this.__ob__ / / the Observer class instance
    /** * Monitor for new elements */
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args // For push and unshift, the arguments passed are the new elements
        break
      case 'splice':
        inserted = args.slice(2) // If splice is subscript 2, this is the new element
        break
    }
    if (inserted) ob.observeArray(inserted) // observeArray converts new elements into responses
    ob.dep.notify()
    return result
  })
})
Copy the code

8. To summarize

Because array change detection is implemented by interceptors that intercept array manipulation methods, we normally use array subscripts to manipulate data without triggering a responsive update of vUE.

Solution: instead of subscripting an array, use an array method. Or use vue.$set