A problem with Vue when operating on arrays

In Vue, we have some problems manipulating arrays. Why is the page not refreshed even though the data has been modified? The answer to this question has been given in the official documentation, the precautions for detecting change

Due to JavaScript limitations, Vue cannot detect array and object changes. However, there are ways to circumvent these limitations and make them responsive.

Vue cannot detect changes to the following arrays:

  1. When you set an array item directly using an index, for example:vm.items[indexOfItem] = newValue
  2. When you modify the length of an array, for example:vm.items.length = newLength

7 ways of packaging

Vue wraps seven methods of Array that operate on the Array to trigger a page refresh.

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

Use of 7 methods

  • The push() method is used to add one or more elements to the end of an array, returning array length

  • The pop() method removes the last element of the array and returns the deleted element

  • The shift() method deletes the first element of an array and returns the deleted element

  • The unshift() method is used to add one or more elements to the head of an array, returning array length

  • The splice() method is used to delete, add, and replace elements,

    • arrayObject.splice(index,howmany,item1,….. ItemX) the first argument index is the subscript, the second argument howmany is the number of deleted, and the third and subsequent arguments are elements added like arrays

    • If only the first element is passed, all elements after index are deleted

    • If two arguments are passed, howmany elements after the start index are removed

    • If you pass three parameters, remove howmany elements after the start index index and add items

  • The sort() method is used to sort arrays by character encoding if no arguments are passed, or by passing a callback function with two parameters and sorting by the return value.

    • If a is less than b, which should precede B in the sorted array, returns a value less than 0.
    • If a is equal to b, 0 is returned.
    • If a is greater than b, return a value greater than 0.
  • The reverse() method is used to reverse the order of elements in an array

The implementation principle of 7 array methods

These 7 methods can trigger the page refresh not because they can be triggered in the first place, but because the 7 methods are wrapped in special logic in Vue

.\src\core\observer\array.js

import { def } from '.. /util/index'

// Store the Array prototype object
const arrayProto = Array.prototype
// Create a prototype for an arrayMethods object pointing to an arrayProto object
export const arrayMethods = Object.create(arrayProto)

// Define an array of 7 method names
const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]

// Iterate through an array of 7 method names
methodsToPatch.forEach(function (method) {
  // Store the original prototype method of Array. It is called here because you only need to fetch it once, using closures to trade space for time
  const original = arrayProto[method]
  // def is a simple wrapper around defineProperty, adding our wrapped methods to the arrayMethods object
  def(arrayMethods, method, function mutator (. args) {
    // Call the original Array prototype method, do the original logic of the method, and then process the added logic
    const result = original.apply(this, args)
    // __ob__ is the Dep object
    const ob = this.__ob__
    // The element added to the array needs to be processed responsively, and the element that needs to be processed is stored here
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // Respond to the added element
    if (inserted) ob.observeArray(inserted)
    // Notify Watcher to update the view
    ob.dep.notify()
    // Return the value of the original method
    return result
  })
})
Copy the code

As you can see, the arrayMethods object with the prototype array. prototype is exposed. In this object, we wrap the 7 methods in Array.

So, once we wrap it, how does it modify our array object in data, because it changes its methods when it responds to the array object, and it changes them in two ways,

  1. In our environment objects have__ob__Property directly points the array’s prototype to oursarrayMethodsObject, that is, add a layer to the prototype chain, which is our arrayMethods, when the array object calls these seven methods, and it can’t find them in the object, it will look for them in the prototype object, and it will call them directly
  2. If there is no__ob__Object, we define these seven methods directly into our array object

.\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) {
        // Change the prototype chain if there is an __ob__ object
        protoAugment(value, arrayMethods)
      } else {
        Otherwise, define methods directly in array objects
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

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

// Modify the prototype chain
function protoAugment (target, src: Object) {
  target.__proto__ = src
}

// Define seven methods into an array object
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

You can see our arrayMethods added to the Books prototype chain

Use vue.set to manipulate arrays

Using the set method to manipulate an array is essentially a call to the splice method.

.\src\core\observer\index.js

export function set (target: Array<any> | Object, key: any, val: any) :any {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
}
Copy the code

Array operations in Vue3

The responsivity principle of Vue3 is to use proxy, which is different from defineProperty used in Vue2 and does not have the above problems. You can modify the array by subscripting or directly modify the array length