Recently in reading Vue source code, from its core principle, began to learn the source code, and its core principle is the response of its data. And combined with the design pattern for learning

Observer mode && Publish subscriber mode

Here’s a brief introduction to the connections and differences between the two models,

Observer mode

There is a version of the observer pattern called the “published-subscribe” or “subscription-publish” pattern, in which subscribers are linked to the subscription target and are notified individually when the subscription target changes. We can use the subscription of newspapers and periodicals to vividly illustrate that when you subscribe to a newspaper, there will be a latest newspaper delivered to your hand every day, and the number of people who subscribe to the newspaper will be the number of newspapers, the newspaper and the customers who subscribe to the newspaper is the “one-to-many” dependency relationship mentioned at the beginning of the above article.

Publish subscriber mode

However, with the precipitation of time, it seems that it has become strong, independent of the observer mode, and become another different design mode.

In the current publis-subscribe model, message senders called publishers do not send messages directly to subscribers, which means that publishers and subscribers are unaware of each other’s existence. A third component exists between the publisher and the subscriber, called the scheduling center or event channel, which maintains the relationship between the publisher and the subscriber, filtering all incoming messages from the publisher and distributing them to the subscribers accordingly.

For example, if you follow A on Weibo, and many other people follow A as well, then when A posts A dynamic, weibo will push this dynamic for you. A is the publisher, you are the subscriber, and Weibo is the dispatch center. There is no direct message exchange between you and A, which is all coordinated through Weibo (your attention and A’s release dynamics).

differences

As you can see, the publish/subscribe pattern has more event channels than the observer pattern. The event channel acts as the scheduling center, managing the subscription and publication of events, and completely isolating the dependencies between subscribers and publishers. That is, when subscribers subscribe to an event, they only care about the event itself, and they don’t care who will publish the event. When publishing an event, the publisher only cares about the event itself, not who subscribed to the event.

The observer pattern has two important roles, the target and the observer. There is no event channel between the target and the observer. On the one hand, if an observer wants to subscribe to a target event, it must add itself to the Subject for management because there is no event channel. On the other hand, the target cannot delegate the notify action to the event channel when the event is fired, so it has to notify all observers in person.

Response principle

When we define a value in data, we do the following:

const vm = new Vue({
    data() {
        return {
            message: ' '}},template: '<div>{{message}}</div>'
})
vm.message = 'hello';
Copy the code

What happens inside Vue at this point, and the problems that need to be solved are listed below:

  1. How is dependency collection done
  2. whendataHow is the view updated when the value in


data
computed

  • The Watcher: subscriber
  • The Observer: the Observer
  • Dep: publisher
  • Data: Data items in the instance

Observer

First, what happens to the data when you instantiate Vue

 _init
    => mount= > this._watcher = new Watcher(vm, updateComponent, noop)
    => Dep.target = this._watcher
    => observe(data, true) = >new Observer(data)
Copy the code
  1. First of all,new VueWill be called_initfunction
  2. mountMount the template that you want to render to the element
  3. To create aWatcherThe instance
  4. Will create aboveWatcherInstance assignment toDep.target
  5. rightdataThe returned data is carried outobserve
  6. callnew ObservertraversedataforsetterandgetterThe binding

Let’s look at the implementation of the observe function:

function observe(value, asRootData) {
    let ob;
    // Check whether the current data has been observed. If yes, the binding does not need to be repeated
    if(hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__
    } else {
        ob = new Observer(value);
    }
    if (asRootData && ob) {
      ob.vmCount++;
    }
    return ob;
}
Copy the code

This function is called first. The __ob__ user determines if an Observer instance exists, uses the original one if it does, and creates a new one if it does not. VmCount indicates the number of times the Vue instance has been used, and asRootData indicates whether the Vue instance is used twice. For example, in a template, the same component is used twice:

<div>
  <my-component />
  <my-component />
</div>
Copy the code

So vmCount is going to be 2. Next, look at the implementation of the Observer:

class Observer {
    constructor(value) {
        this.value = value;
        this.dep = new Dep();
        this.vmCount = 0;
        def(value, '__ob__'.this)
        if (Array.isArray(value)) {
            // If it is an array, you need to traverse each member of the array to perform the observe
            // This redefines the array's original methods
            this.observeArray(value)
        } else {
            // If the object is an object, then call the following program
            this.walk(value)
        }
    }
    walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i], obj[keys[i]])
        }
    }
}
Copy the code

Below is the structure of the Observer class

data
__ob__
__ob__
Dep
observeArray
observe
defineReactive
getter/setter
defineReactive
Vue
defineReactive(obj, keys[i], obj[keys[i]])
data

function defineReactive(obj, key, val) {
  // Define a DEP object in the closure
  const dep = new Dep();
  // The child object of the object recurses to observe and returns the Observer object of the child node
  let childOb = observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        Dep. Depend is to bind dep and watcher to each other
        Dep.target indicates the watcher to be bound
        dep.depend();
        if (childOb) {
          // The same watcher observer instance is placed in two Depend
          // One is the depend in its own closure, and the other is the depend of the child element
          childOb.dep.depend();
        }
        if (Array.isArray(value)) {
          // In the case of an array, a dependency collection is required for each member of the array
          dependArray(value)
        }
      }
      return value;
    },
    set: function reactiveSetter(newVal) {
      // Get the current value from the getter method and compare it with the new value. If it is the same as the new value, do not need to perform the following operations
      const value = getter ? getter.call(obj) : val;
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return false;
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // The new value must be re-observed to ensure that the data is responsive
      childOb = observe(newVal)
      // Notify all observers
      dep.notify()
    }
  })
}
Copy the code

The data is bound to getters and setters with object.defineProperty. Getters are used for dependency collection, and setters are used to notify Watcher via DEP, and watcher performs changes. How to do dependency collection can be illustrated with an example:

data() {
  return {
    message: [1.2]}}Copy the code

Analyze the above example with a flow chart:

observe(data)
=> data.__ob__ = new Observer(data)
=> walk(data)
=> childOb = observe(message)
  => message.__ob__ = new Observer(data)
  => message.__ob__.dep = new Dep;
=> childOb ? childOb.dep.depend();
Copy the code

The analysis process is:

  1. On the firstdataFunction returns an object to be added__ob__, return the following details:
const res = {
  message: [1.2]
  __ob___: new Observer(data)
}
Copy the code
  1. traverseresBecause theresIs an object, so executewalk
  2. Perform toobserve(message)
  3. tomessageadd__ob__.__ob__There is adepFor dependency collection
  4. childOb = message.__ob__, the same timewatcherIn a child object, which ismessage.__ob__.depIn reviewing the above analysis, it is possible to distinguishObserveranddefineReactiveIn the twodepThe difference between these two placesnew Dep.ObserverthedepA subscriber used to collect objects and arrays, mounted on the properties of the object. Called when elements are added or deleted from an object or array$setTo obtain the__ob__Do a dependency collection, and then callob.dep.notifyJ to update. indefineReactiveIn thedepThere is a closure that serves the object properties, collects dependencies when the property value is obtained, and publishes updates when the property value is set.

Dep

Let’s introduce deP, the source code is as follows:

let uid = 0;
class Dep {
  constructor() {
    this.id = uid++;
    this.subs = []
  }
  // Add a subscriber
  addSub(sub) {
    this.subs.push(sub)
  }
  // Remove an observer object
  removeSub(sub) {
    remove(this.subs, sub)
  }
  // Dependencies collection, adding observer objects when dep. target exists
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)}}// Notify all subscribers
  notify() {
    const subs = this.subs.slice();
    for(let i = 0, l = subs.length; i < l; i++) { subs[i].update(); }}}Copy the code

The structure is as follows:

get
defineReactive
const dep = new Dep()
Watcher
subs
Dep
Watcher
Dep
data

data() {
  return {
    message: 'a'}}Copy the code

There are two ways to trigger a view:

  1. usinggetter/setter, resetmessageIs triggered during the settingdep.notifyMake release updates, such asthis.message = 'b'
  2. use$setFunction:this.$set(this.message, 'fpx', 'number-one')That will get youmessagethe__ob__On thedepMake release updates

Watcher

Watcher is a subscriber. After dependency collection, watcher is stored in the Dep subs. When data changes, the Dep publisher publishes the information, and the relevant subscriber Watcher receives the information and updates the view via CB. Watcher has a lot of content, so we’ll just focus on the most important parts:

class Watcher {
  constructor(vm, expOrFn, cb, options) {
    this.vm = vm;
    // Store the subscriber instance
    vm._watchers.push(this)
    this.deps = [];
    this.newDeps = []
    this.depsIds= new Set(a);this.newDepIds new Set(a);if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.value = this.get();
  }
  get() {
    pushTarget(this)
    const vm = this.vm;
    value = this.getter.call(vm, vm);
    popTarget();
    this.cleanupDeps();
    return value
  }
  // Add a dependency to the Deps collection
  addDep(dep) {
    const id = dep.id;
    if (!this.newDepsIds.has(id)) {
      this.newDepsIds.add(id)
      this.newDeps.push(dep);
      // If depIds contains this id, then add this id to depIds
      Dep. AddSub (this) is already called
      if (!this.depIds.has(id)) {
        dep.addSub(this)}}}// To update the template
  update() {
    if (this.sync) {
      // To synchronize, run directly renders the view
      this.run();
    } else {
      // Push it asynchronously to the observer queue, called on the next tick, and finally call the run method
      queueWatcher(this)}}// Collect all deps principles for the watcher
  depend() {
    let i = this.deps.length;
    while(i--) {
      this.deps[i].depend(); }}}Copy the code

The Watcher structure is as follows:

Watcher

Dep.target = newWatcher(VM, updateComponent, noop = {}) => Initialize the variable => Get the getter function => call the get function, which calls the getter function, thus collecting dependenciesCopy the code

When creating a Vue instance, triggering the getter triggers dependency collection. Here are a few cases: Watcher has four scenarios in which it collects dependencies and updates templates or expressions

  1. Look at the data in the template
  2. createVueWhen an instancewatchThe data in the options
  3. computedThe data that the data in the selection depends on
  4. use$watchThe observed data or expression is declared in the previous codeDep.targetWhat is this for? As mentioned earlier, the timing of dependency collection is when we get the value of an element attribute, but we don’t know which one is correctwatcher, so define a global variable to record the currentWatcherTo add the current executionWatcher.WatcherObjects have two properties:depsandnewDeps. They use it to record the last timeWatcherCollection of dependencies and a new roundWatcherThe collected dependencies need to be collected again every time the data is updated. The process is as follows:
setter
  => notify= > run
  => get
Copy the code

When the data is published and updated, the notify method is called, and the notify method calls the run method, and the run method calls the GET method to retrieve the value and redo the dependency collection. As an example above, if we change the value of message and the template depends on the new value, this.message = {key: ‘val’}, the dependencies need to be collected again this round because the new value was not dependent on the previous round.

ComputedThe principle of

We have examples in the business, how does message know and update when A changes

data: {
    return {
        a: 1}},computed: {
    message() {
        return this.a
    }
}
Copy the code

During state initialization, data, Watch, and computed are initialized

function initComputed(vm, computed) {
    const watchers = vm._computedWatchers = Object.create(null)
    for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        
        watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions,
        )
        if(! (keyin vm)) {
            defineComputed(vm, key, userDef)
        }
    }
}
Copy the code

As you can see, a watcher is created for each of the computed properties that are created. DefineComputed mounts the declared attributes to the VM and requires that they not be the same as the values of the attributes declared by data and Prop. DefineComputed uses object.defineProperty for attributes defined in computed. When the GET operation is triggered, the EVALUATE of watcher is triggered

// evaluate
this.value = this.get();
Copy the code

Target is set to the current computed watcher because of the fact that Vue is initialized with a value in the data, and when the get operation is triggered, the current watcher is added to the Dep of the current data. The next time a set operation occurs on the data, the watcher will be notified of the update.

function createComputedGetter(key) {
    return function computedGetter() {
        const watcher = this._computedWatcher && this._computedWatchers[key]
        if (watcher) {
            watcher.evaluate()
        }
        if (Dep.target) {
            watcher.depend();
        }
        return watcher.value
    }
}
Copy the code

Watcher.depend () is what this does, for example:

computed: {
    message1() {
        return 1;
    },
    message2() {
        return this.message1; }}Copy the code

If one computed is dependent on another computed, as mentioned earlier, each computed corresponds to a watcher, so you need to access Message1 before getting message2. In this case, add the watcher corresponding to Message1 to the Dep corresponding to Message2.

conclusion

When Vue is initialized, a watcher is generated, and dependency collection is done through getters for properties. In combination with the picture given at the beginning of this article, the relationship between Observer and Dep is one-to-one, while the relationship between Dep and Watcher is many-to-many, and Dep is the link between Observer and Watcher. Depending on the collection completion, the dep.notify() method of the observer-object is executed when the property changes. This method iterates over the list of subscribers to send a message to the Watcher, and the Watcher executes the run method to update the view.

I was going to do a little bit of computed, but I think you’re tired looking at it, I’m tired writing about it, computed will be covered in another article. It is quite difficult to write down an article. Here are three:

  1. Too much code: Because the source code considers so many cases, when we analyze a single point, we need to discard other code that is not necessary
  2. A running ledger: a record of what each line of code does, with no further exploration
  3. Organic combination: after the analysis, can not be combined with the knowledge learned before

So here are some measures to remedy these problems:

  1. Use as little code as possible and draw a picture of the process. The picture is more intuitive than the code
  2. Think from the point, extend to the line, extend to the plane
  3. Combined with previous knowledge, such as the design pattern here, combined with learning to write this source code analysis article for the first time, many shortcomings, welcome to put forward valuable suggestions, also please pay more attention to my GitHub~~

Log

  1. 12-5updatecomputedThe principle of

To be updated

Watcher principle