Does changing data through array indexes in VUE change the view?

With this question in mind, write vUE responsive data principles with me to find out.

1. Export the vue constructor

import {initMixin} from './init';

function Vue(options) {
    this._init(options);
}
initMixin(Vue); // Add the _init method to the prototype
export default Vue;
Copy the code

2. Initialize the VUE status in init

The init method is first mounted on the Vue prototype. The options passed in by the user are mounted on the VM’s $options

import { initState } from "./state";
export function initMixin(Vue) {
  Vue.prototype._init = function (options) {
    const vm = this;
    vm.$options = options;
    initState(vm);
  };
}
Copy the code

3. Perform initialization operations based on different attributes

Initialize data

Initialize vm data (props, Data, computed, and watch) using initSate. Today we are going to focus on data initialization.

export function initState(vm) { // State initialization
    const opts = vm.$options;
    if (opts.data) {
        initData(vm);
    }
    // if(opts.computed){
    // initComputed();
    // }
    // if(opts.watch){
    // initWatch();
    // }
}
function initData(vm) {
  let data = vm.$options.data;
  // Data can be written in two ways: attributes and functions
  // So we need to judge the data type before passing in the observation data
  data = vm._data = isFunction(data) ? data.call(vm) : data;
  // If data is a function, call this to the VM and immediately execute the data function, assigning the data object returned by the function to vm.\_data, otherwise return data directly
  // The vm has nothing to do with data, and is associated with _data
  observer(data);
}
Copy the code
export function observe(data) {
  // Only if it is an object
  if(! isObject(data)) {return;
  }
// An object returns an instance of an Observer class to observe data
  return new Observer(data);
}
Copy the code

4. Data hijacking

step1.hijacking every key of data.key

First use walk method to traverse data, redefine each attribute of data with defineProperty, add get and set attribute accessors.

One thing to note:

Add a new attribute to V. _data b=1. Because the data is traversed using an existing key on the original data.

step2[key]. Hijack data

Then, the data in data[key] is hijacked. The hijacking methods of array and object are different.

In the first case, observe all entries of the object when data[key] is the object

There are two points to note:

  1. If it’s bread in the objectobjectAnd observe the object recursively.
  2. If you assign an old value to aobjectObserve the new value as well.
import { isObject } from ".. /utils";
import { arrayMethods } from "./array";

// 1. If the data is an object, the object is constantly hijacked recursively
// 2. If it is an array, it hijacks the array's methods and checks for non-primitive data types in the array

class Observer {
    constructor(data) { // Hijack all attributes of the object
        Object.defineProperty(data,'__ob__', {value:this.enumerable:false // Set it to non-enumerable, so it cannot be iterated, so it can solve the stack overflow problem caused by iterating __ob__ multiple times.
        })
        // observeArray (observeArray, observeArray, observeArray, observeArray, observeArray, observeArray, observeArray, observeArray, observeArray, observeArray)
        if(Array.isArray(data)){
            // Array hijacking logic
            // Rewrite the original array method, slice programming higher-order functions
            data.__proto__ = arrayMethods;
            // If the data in the array is of object type, you need to monitor the changes of the object
            this.observeArray(data);
        }else{
            this.walk(data); // Object hijacking logic}}observeArray(data){ // Hijack recursion again for our arrays and objects in arrays
        // [{a:1},{b:2}]
        data.forEach(item= >observe(item))
    }
    walk(data) { / / object
        Object.keys(data).forEach(key= >{ defineReactive(data, key, data[key]); }}})// Vue2 iterates through the object and redefines each attribute with a defineProperty
function defineReactive(data,key,value){ // Value can be an object
    observe(value); // The default value of the user itself is the object set. Objects need to be handled recursively (poor performance).
    Object.defineProperty(data,key,{
        get(){
            return value
        },
        set(newV){ 
            // todo... Update the view
            observe(newV); // If the user assigns a new object, the object needs to be hijackedvalue = newV; }})}export function observe(data) {
    // Only if it is an object
    if(! isObject(data)) {return;
    }
    if(data.__ob__){
        return;
    }
    // The default outermost data must be an object
    return new Observer(data)
}

Copy the code

The second case: when data[key] is an array: rewrite the array prototype method

Arr [3]=100. Vue directly listens to the array index, which causes serious performance consumption. So instead of using objectProperty, we override the array method to listen on the array

  1. push shift unshift pop reverse sort spliceIf the user calls the above seven methods, vUE overrides will be called, otherwise the original array methods will be used.

    Why only these seven?

    Because these seven methods change the array method, others like concat don’t change the original array.

  2. The array does not monitor index changes, but if the data in the array isobjectType, which requires the observe object change to the object. So ifarr:[{name:"chibaozi"}]So it looks like thisVm.arr [0]. Name =" morning eat steamed stuffed bun"It can be listened on by subscript modification.
  3. The new data in the array isobjectType, you also need to observe the added object.
let oldArrayPrototype = Array.prototype
export let arrayMethods = Object.create(oldArrayPrototype);
// arraymethods.__proto__ = array.prototype
let methods = [
    'push'.'shift'.'unshift'.'pop'.'reverse'.'sort'.'splice'
]
methods.forEach(method= >{
    // If the user calls the above seven methods, I will use my own rewrite, otherwise use the original array method
    arrayMethods[method] = function (. args) { // args is the argument list arr.push(1,2,3)
        oldArrayPrototype[method].call(this. args);/ / arr. Push (1, 2, 3);
        let inserted;
        let ob = this.__ob__; // Get an observer instance based on the current array
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args ; // This is the new content
                break;
            case 'splice':
                inserted = args.slice(2)
            default:
                break;
        }
        // If there is new content to continue hijacking, I need to observe each item in the array, not the array
        // Update operation.... todo...
        if(inserted) ob.observeArray(inserted)
    }
})

Copy the code

5. Data broker

We also need to delegate vm._data.key to vm.key, also via defineProperty.

function vmProxy(vm, source, key) {
  Object.defineProperty(vm, key, {
    get() {
      return vm[source][key];
    },
    set(newV){ data[source][key] = newV; }}); }function initData(vm) { //
    let data = vm.$options.data; // vm.$el vue checks for attributes that start with $
    // Vue2 will data hijack all data in data object.defineProperty
    // The vm has nothing to do with data, and is associated with _data
    data = vm._data = isFunction(data) ? data.call(vm) : data;
    // The user goes to vm. XXX => vm._data.xxx
    for(let key in data){ // vm.name = 'xxx' vm._data.name = 'xxx'
        proxy(vm,'_data',key); 
    }
    observe(data);
}
Copy the code

summary

Now, I’m sure you have the answer. Changing the index and length of an array in VUe2 is not monitored.

In Vue2, each attribute of a data Object is iterated internally through the defineReactive method, and the attribute is hijacked using Object.defineProperty. (only hijacks existing attributes). For performance reasons, object.defineProperty is not used to intercept each entry of the array. Arrays are implemented by overriding the array method. You need to change the array by pop Splice shift unshift Reverse sort to trigger the watcher update. If the array is an object data type, it will also be recursively hijacked. Vue3 uses proxy instead for reactive data. Internal dependency collection means that each attribute has its own DEP attribute to store the watcher it depends on. When the attribute changes, the watcher of its own object is notified to update.

After a deeper understanding of the reactive principle, we can also summarize some methods of vUE code optimization:

  1. Do not nest too many layers of the object hierarchy. Because multiple layers of objects in VUE2 are hijacked recursively, the object hierarchy is too deep and performance is poor.
  2. Don’t put anything in data that doesn’t need to be responsive.
  3. You can use Object.freeze () to freeze data

Next article will be compiled by handwriting template, heh heh.