Why rely on collections?

Here’s an example:

We now have such a Vue object.

  new Vue({
    template: `
      
{{text1}} {{text2}}
`
.data: { text1: 'text1'.text2: 'text2'.text3: 'text3',}})Copy the code

And then we did this operation.

  this.text3 = "modify text3";
Copy the code

We modify text3 in data, but because text3 is not needed in the view, we do not need to trigger the cb function described in the previous chapter to update the view. Calling cb is clearly not correct.

Here’s another example:

Assuming we now have a global object, we might use it to demonstrate it in multiple Vue objects.

  let globalObj = {
    text1: 'text1'};let o1 = new Vue({
    template: ` 
      
{{text1}}
`
.data: globalObj }); let o2 = new Vue({ template: `
{{text1}}
`
.data: globalObj }); Copy the code

At this point, we do the following.

  global.text1 = 'hello,text1';
Copy the code

We should notify both VM instances O1 and O2 to update the view. Dependency collection lets text1 know “Oh, there are two places that depend on my data and I need to notify them when I change”.

Finally, a corresponding relationship between data and view will be formed, as shown in the following figure.

Next, let’s look at how dependency collection is implemented.

The subscriber Dep

Let’s start by implementing a subscriber Dep, whose main purpose is to hold the Watcher observer object.

  class Dep {
    constructor () {
      // An array of Watcher objects
      this.subs = [];
    }

    // Add a Watcher object to subs
    addSub (sub) {
      this.subs.push(sub);
    }

    // Notify all Watcher objects to update the view
    notify () {
      this.subs.forEach((sub) = >{ sub.update(); }}})Copy the code

We only implemented part of the added code for ease of understanding, mainly for two things:

  • withaddSubMethod to add a watcher subscription operation to the current Dep object;
  • withnotifyMethod notifies all Watcher objects in the subs of the current Dep object to trigger an update operation;

Observer Watcher

  class Watcher {
    constructor () {
      // Assign a new watcher object to dep. target, which is used in get
      Dep.target = this;
    }

    // Update the view method
    update () {
      console.log("View updated ~")
    }
  }

  Dep.target = null;
Copy the code

Depend on the collection

Next we modify defineReactive and the Vue constructor to do the dependency collection.

We add an object of class Dep to the closure to collect Watcher objects. When the object is read, the reactiveGetter function is triggered to collect the current Watcher object (stored in dep.target) into the Dep class. Then, if the object is written, the reactiveSetter method is triggered, notifying the Dep class to call notify to trigger the Update method on all Watcher objects to update the corresponding view.

  function defineReactive (obj, key, val) {
    // a Dep object
    const dep = new Dep();

    Object.defineProperty(obj, key, {
      enumerable: true.configurable: true.get: function reactiveGetter () {
        // Put dep. target (the current watcher object into the subs of the Dep)
        dep.addSub(Dep.target);
        return val;
      },
      set: function reactiveSetter (newVal) {
        if (newVal === val) return;
        // Trigger notify of deP on set to notify all Watcher objects to call their update method to update the viewdep.notify(); }})}class Vue {
    constructor (options) {
      this._data = options.data;
      observer(this._data); // Normally this will be followed by an Object, keys(data) recursively calling defineReactive
      // Create a new Watcher observer object. Dep. target points to the watcher object
      new Watcher();

      // Simulate the render process here, in order to trigger the get function of the test property
      console.log(`render~: The ${this._data.test}`); }}Copy the code

summary

To sum up.

The get method is first registered in the Observer procedure, which is used for dependency collection (dep.addSub(dep.target)). It will have a Dep object in its closure that will hold an instance of the Watcher object. In fact, the dependency collection process is to store the Watcher instance into the corresponding Dep object. The get method stores the current Watcher object (dep.target) in its subs (addSub) method. The set calls the notify method of the Dep object to notify all its internal Watcher objects to update the view with its update method. (vm. _update (vm) and _render ()))

This is what object.defineProperty’s set/get method handles, so there are two more prerequisites:

  • The triggergetMethods;
  • Create a newWatcherObject;

We handle this in Vue’s constructor class. To create a new Watcher object, you just need to come out new, and dep. target already points to the new Watcher object. In fact, as long as the render function is rendered, all the dependent objects will be [read]. Here we simulate the process by printing, reading test to trigger get for [dependency collection].

In this chapter we have introduced the process of “dependency collection” and have covered the whole “responsive system” in conjunction with the previous reactive principle. The main thing is get for “dependency collection.” Set updates the view through the viewer.

Note: Refer to “Principles of Dependency Collection tracing for Responsive Systems” for this code.