Question to consider

Let’s look at a piece of code and think about a few questions


<template>
    <div>
        <div v-for="item in testArr" :key='item'>
            {{item}}
        </div>
    </div>
</template>
export defaut {
    data() {
        return {
            testArr: [1.2.3]}},method: {
        changeArr() {
            this.testArr = [1.2.3.4]},changeLength() {
            this.testArr.length = 2
        },
        changeArrItemByIndex() {
            this.testArr[0] = 100;
        },
        addArrItem() {
            this.testArr.push(4); }}}Copy the code
  1. When willtestArrArray direct assignment, executechangeArrMethods;
  2. When changing the length property of the data, executechangeLengthMethods;
  3. Executes when changing an element of data by array subscriptchangeArrItemByIndexMethods;
  4. With the push method, executeaddArrItemMethods;

In the above four cases, think about whether the page data will be updated?

Array responsivity principle analysis

Vue uses object.defineProperty to add get and set methods to the data, then does dependency collection when calling get and notifying watcher objects to update the data when the set changes. Arrays have a different situation, we look at the source.

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    Object.defineProperty
    def(value, '__ob__'.this)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
Copy the code

As you can see from the code in the Observer class, it does something special when the data it is listening for is an array. Now let’s parse this code.

hasProto

export const hasProto = '__proto__' in {}
Copy the code

Check whether the object in the current browser has a __proto__ attribute for browser compatibility.

protoAugment

The protoAugment method is called if the current browser supports the __proto__ attribute, so let’s look at the code

function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
Copy the code

This method also has very little code, and essentially sets the prototype we passed in to the array to the arrayMethods we passed in.

Let’s look at the code for arrayMethods

/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ import { def } from '.. /util/index' const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ 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__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result })})Copy the code

You can see that with Object.create(arrayProto) you create an Object that is a prototype of the original array, and then you store a bunch of array methods in the methodsToPatch array, and they all have one thing in common: they all change the original array. The purpose of this is to make it easier to add listeners to modify the array methods when they are executed.

ObserveArray method code

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

Def method code

The def method sets the key of the object.

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

Add a listener for each item of data.

copyAugment

The copyAugment method is always the same as the protoAugment method, adding listeners to the array but dealing with browser compatibility issues.

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

conclusion

The reactive data of an array in VUE is obtained by modifying the original array method and then listening for each item in the array.

Vue does not handle 2,3, and is only responsive when it calls several methods that change the array. In case 1, the direct assignment is handled by vue’s reactive implementation, so only 2,3 does not update the view.