Earlier, we talked about the data detection mechanism (Observer) in VUE, if implemented to listen for changes in object and array data. However, if we only know the data changes, we can’t update the data to the view in time. Therefore, we need to collect the dependencies, and when the data is updated, we will trigger the collected dependencies once again, so that the changes in the data can be updated to the view.

Collection depends on Dep

For objects, dependencies are collected in getters and triggered in setters. So where are dependencies stored? Vue uses a Dep class to manage dependencies. For each key value of the response data object, there is an array to store dependencies. Let’s start with the Dep class, which helps us collect dependencies, remove dependencies, and trigger dependencies.

export default class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  removeSub(sub) {
    remove(this.subs,sub)
  }

  depend() {
    if(Window.target) {
      this.addSub(window.target)
    }
  }
  notify() {
    const subs = this.subs.slice();
    for(let i=0,len = subs.slice.length; i<1; i++) { subs[i].update() } } }function remove(arr,item) {
  if(arr.length) {
    const index = arr.indexOf(item);
    if(index > - 1) {
      return arr.splice(index,1)}}}}Copy the code

With the Dep class, let’s modify the defineReactive function:

function defineReactive(obj,key,value) {
    // The value of the recursive object is also monitored if the value is an object
    observer(value);
    let dep = new Dep()
    Object.defineProperty(obj,key,{
        enumerable:true.configurable: true,
        get() {
            // For objects we collect dependency watcher here
            dep.depend()
            return value
        },
        set(newValue) {
            if(value == newValue) {
                return;
            }
            // Object: This is where the collected dependency is triggered
            value = newValue;
            dep.notify()
            // A key may also be an object that needs to be listened on
            observer(newValue);
            
             console.log('View Update'); }})}Copy the code

Now that you’ve collected your object’s dependencies, what exactly are dependencies?

Rely on the watcher

In the above code, we collect dep. target, which is watcher. It is an abstract class that can process the data used by the page or the watch written by the user.

export default class Watcher{
  constructor(vm,expOrFn,cb) {
    // Vue instance object
    this.vm = vm;
    // Execute the getter to get the value passed in by the user such as data.a.b.c
    this.getter = parsePath(expOrFn);
    this.cb = cb;
    // Triggers getter collection dependencies
    this.value = this.get()
  }
  get() {
    window.target = this;
    // Get a new value
    let value = this.getter.call(this.vm,this.vm);
    window.target = null;
    return value;
  }

  update() {
    const oldVaule = this.value;
    this.value = this.get();
    this.cb.call(this.vm,this.value,oldVaule)
  }
}
Copy the code

We now set window.target to this in the get method, which is the current instance of Watcher, and then read the passed property to its initial value (old value), which triggers the getter collection dependent on the Watcher into the deP for the corresponding key. Later, when the key such as data.a.b.c changes, the setter is used to execute Watcher’s update method to get the latest state of the data. The vm.$watch(‘ A.B.C ‘,cb) and the data bound by instructions and interpolation syntax in the template are all based on Watcher. And how parsePath works:

const reg = /[^\w.$]/
export function parsePath(path) {
  if (reg.test(path)) {
    return;
  }
  const args = path.split('. ');
  // obj in watcher value this.vm
  return function (obj) {
    for (let i = 0, len = args.length; i < len; i++) {
      if(! obj) {return
      }
      obj = obj[args[i]]
    }
  }
}
Copy the code

Characters are separated by a ‘. ‘and then evaluated from data layer by layer.

The object problem

At this point, data change detection for type Object and dependency collection triggers are clear. But getter/setter tracing, you can’t listen for new properties anddeleteAnd dependencies are not notified. But there are two apis officially mentioned: VM.Delete to fill in the hole.

An array of

Arrays have many prototype methods, and the methods we use to alter arrays in VUE are provided by internal interceptors. In order to cooperate with the dependency collection of the array, we modify the observe function.

export class Observer {
  constructor(value) {
    this.value = value;

    if (!Array.isArray(value)) {
      / / object
      this.walk(value)
    }
  }
  /* Walk listens for each property of the object */
  walk(obj) {
    const keys = Object.keys(obj);
    for(let i=0; i<keys.length; i++) { defineReactive(obj,keys[i],obj[keys[i]]) } } }function defineReactive(data,key,val) {
  if(typeof val == 'object') {
    new Observer(val)
  }
  Object.defineProperty(data,key,{
    enumerable: true.configurable: true,
    get() {
      dep.depend();
      return val
    },
    set(newVal){
      if(val == newVal) {
        return
      }
      val = newVal;
      dep.nofify()
    }
  })
}
Copy the code

In transforming the function of the Observer, we quickly under the realization method of data interceptor modify array of commonly used methods of push and pop, shift, unshift, splice, sort, the reverse:

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(null);
['push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'].forEach(function(method) {
  // Cache the method on the prototype
  const original = arrayProto[method];
  Object.defineProperty(arrayMethods,method,{
    enumerable: false.writable: true.configurable: true.value:function mutator(. args) {
      return original.apply(this,args)
    }    
  })
})
Copy the code

After customizing the original method of manipulating arrays, we can send notifications and trigger dependencies in mutator functions. The prototype is then overwritten with an interceptor

export class Observer {
  constructor(value) {
    this.value = value;

    if (Array.isArray(value)) {
        // Overwrite the prototype
      value.__proto__ = arrayMethods;
    }else{
      / / object
      this.walk(value)
    }
  }
    
}
Copy the code

Array collection dependency

In fact, array dependencies are also collected in the getter, because arrays are also accessed through data’s key, such as this.list, which triggers the getter for the list property. So arrays collect dependencies in the getter and fire in the interceptor. The array’s dependencies are then stored in an Observer and accessed by the interceptor:

export class Observer {
  constructor(value) {
    this.value = value;
    // This deP collects all object and array dependencies, as well as the set and DELETE APIS
    this.dep = new Dep();
    if (Array.isArray(value)) {
        // Overwrite the prototype
      value.__proto__ = arrayMethods;
    }else{
      / / object
      this.walk(value)
    }
  }
}
Copy the code

After storing the DEP on the Observer property, we can collect dependencies on the getter

function defineReactive(data,key,val) {
  let childOb = observe(val);
  if(typeof val == 'object') {
    new Observer(val)
  }
  Object.defineProperty(data,key,{
    enumerable: true.configurable: true,
    get() {
      if(childOb) {
        // Collect dependencies again
          childOb.dep.depend()
      }
      dep.depend();
      return val
    },
    set(newVal){
      if(val == newVal) {
        return
      }
      val = newVal;
      dep.nofify()
    }
  })
}
/* returns an Observe instance for value. We can access the DEp in the interceptor. If created successfully, return an Observer instance
export function observe(value,asRootData) {
    if(typeOf value ! ='object') {
        return
    }
    let ob;
    if(value.hasOwnProperty('__ob__') && value.__ob__ instanceof Observer ) {
        ob = value.__ob__;
    }else{
        ob = new Observer(value)
    }
    return ob
}
Copy the code

In this way, we can collect the dependencies of both objects and arrays in the getter into the DEP of the Observer instance, so that we can notify the dependencies in the interceptor via value.ob.dep. Here I also mark whether the current value has been converted to responsive data by the Observer, so add a line of code to the Observer:

class Observer{
    constructor(value) {
        ...
        def(value,'__ob__'.this)... }}function def(obj,key,val,enumerable) {
    Object.defineProperty(obj,key,{
        enumerable:!!!!! enumerable,writable: true.configurable: true,
        val
    })
}
Copy the code

Next we need to listen on each item of the array and send a notification in the interceptor:

class Observer{
    constructor(value) {
        this.value = value
        def(value,'__ob__'.this)... if(Array.isArray(value)) {
            // Listen for each item in the array
            this.observeArray(value)
        }else{
            this.walk(value)
        }
    }
    observeArray(val){
        for(let i=0; i<val.length; i++) { observe(val[i]) } } }Copy the code

The interceptor

const arraryProto = Array.prototype;
// The method on the array prototype
let proto = Object.create(arrayProto);
['push'.'unshift'.'splice'.'reverse'.'sort'.'shift'.'pop'].forEach(method= >{
    proto[method] = function (. args) {
        const ob = this.__ob__
        // Just like Object, we also need to add data to arrays. Push unshift and splice can add data
        let inserted; // No new data is inserted by default
        switch(method) {
            case 'push':
            case 'unshift':
                inserted = args
                break;
            // The array splice must pass three arguments to add data to the array
            case 'splice':
                inserted = args.slice(2)
                break;
            default:
                break;
        }
         console.log('View Update');
        // Check the new data
        if(inserted) {
            ob.observeArray(inserted)
        }
        // Send dependencies
        ob.dep.depend()
        // We still call the prototype array method, but we can send the array change notification here
        arrayProto[method].call(this. args) } })Copy the code

List [0] = 1,this.list. Length =0; this.list. I can’t trace it.