Recently, some students mentioned the Vue responsive principle in the department sharing. During the discussion, we found that some students did not understand this knowledge deeply enough to form a closed loop. In order to help you understand this problem, I reviewed the Vue source code and sorted out several flow charts for your understanding.

  • Vue initialization
  • Template rendering
  • The component rendering

This article Vue source version: 2.6.11, in order to facilitate understanding, have been deleted.

This paper will explore from the following two aspects:

  • The process from Vue initialization to DOM generation by first rendering.

  • The flow from Vue data modification to page DOM update.

Vue initialization

Let’s start with the simplest Vue code:

<template> <div> {{ message }} </div> </template> <script> new Vue({ data() { return { message: "hello world", }; }}); </script>Copy the code

This code is simple and will eventually print a Hello World on the page. How does that work?

We start our analysis at the source: New Vue.

// The following methods are executed in sequence when new Vue is executed
// 1. Vue.prototype._init(option)
// 2. initState(vm)
// 3. observe(vm._data)
// 4. new Observer(data)

// 5. Call the walk method to walk through each attribute of data and listen for changes in data.
function walk(obj) {
  const keys = Object.keys(obj);
  for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); }}// 6. Execute defineProperty to listen for data reading and setting.
function defineReactive(obj, key, val) {
  // Create a Dep for each attribute (collect dependent container, more on that later)
  const dep = new Dep();
  // bind get and set
  Object.defineProperty(obj, key, {
    get() {
      const value = val;
      // If there is a target identifier, the dependency collection is performed
      if (Dep.target) {
        dep.depend();
      }
      return value;
    },
    set(newVal) {
      val = newVal;
      // Inform the page to re-render when data is modifieddep.notify(); }}); }Copy the code

Once the data descriptor binding is complete, we have the following flow chart:

In the figure, we can see that when the Vue is initialized, the data is bound to get and set, and a Dep object is created.

We’re familiar with get and set bindings for data, but what about Dep objects?

The Dep object is used for dependency collection. It implements a publish-subscribe mode, completing the subscription of Data and render view Watcher. Let’s examine it together.

class Dep {
  // Dep.target is a Watcher type.
  statictarget: ? Watcher;// subs stores the collection of Watcher objects
  subs: Array<Watcher>;
  constructor() {
    this.subs = [];
  }
  addSub(sub: Watcher) {
    // Collect all the Watcher objects that use this data.
    this.subs.push(sub);
  }
  depend() {
    if (Dep.target) {
      // Collect dependencies, which will eventually call the addSub method above
      Dep.target.addDep(this); }}notify() {
    const subs = this.subs.slice();
    for (let i = 0, l = subs.length; i < l; i++) {
      // Call the corresponding Watcher to update the viewsubs[i].update(); }}}Copy the code

Based on the source code analysis of Dep, we get the following logic diagram:

Now that we know about Data and Dep, let’s continue to uncover Watcher.

class Watcher {
  constructor(vm: Component, expOrFn: string | Function) {
    // Assign the vm._render method to the getter.
    // expOrFn is actually vm._render, which will be discussed in the following article.
    this.getter = expOrFn;
    this.value = this.get();
  }
  get() {
    // Assign dep. target to the current Watcher object
    Dep.target = this;
    // this.getter is vm._render
    // vm._render generates the virtual DOM, performs dom-diff, and updates the real DOM.
    const value = this.getter.call(this.vm, this.vm);
    return value;
  }
  addDep(dep: Dep) {
    // Add the current Watcher to the Dep collection pool
    dep.addSub(this);
  }
  update() {
    // Start the asynchronous queue and update Watcher in batches
    queueWatcher(this);
  }
  run() {
    // As with initialization, the get method is called to update the view
    const value = this.get(); }}Copy the code

Dep = Dep = Dep = Dep = Dep = Dep = Dep = Dep = Dep = Dep = Dep; With the above Data, the current relationship among Data, Dep and Watcher is as follows:

Let’s go through the whole process again: Vue does the proxy for all the Data in the Data through defineProperty. When the Data triggers a GET query, the current Watcher object is added to the dependency collection pool Dep. When the Data changes, Set is triggered to tell all Watcher objects that use this Data to update the view.

The current overall process is as follows:

In the flow above, both Data and Dep are created when the Vue is initialized, but now we don’t know where Wacher is created. With this question in mind, we explore further.

Template rendering

In the previous section, we looked at the data processing part of the Vue initialization process. Next, we looked at the data rendering part.

At the end of the new Vue execution, the mount method is called to render the Vue instance into a DOM.

// new Vue executes the process.
// 1. Vue.prototype._init(option)
// 2. vm.$mount(vm.$options.el)
// 3. render = compileToFunctions(template), template in Vue, generate render method.
Prototype.$mount calls the render method above to mount the DOM.
// 5. mountComponent

// 6. Create Watcher instance
const updateComponent = () = > {
  vm._update(vm._render());
};
// updateComponent is the getter passed into Watcher.
new Watcher(vm, updateComponent);

// 7. New Watcher executes the watcher.get method
// 8.watcher. Get executes this.getter.call(vm, vm), which executes the updateComponent method
UpdateComponent executes vm._update(vm._render()).

// 10. Call vm._render to generate the virtual DOM
Vue.prototype._render = function () :VNode {
  const vm: Component = this;
  const { render } = vm.$options;
  let vnode = render.call(vm._renderProxy, vm.$createElement);
  return vnode;
};
Call vm._update(vnode) to render the virtual DOM
Vue.prototype._update = function (vnode: VNode) {
  const vm: Component = this;
  if(! prevVnode) {// First render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false);
  } else {
    / / updatevm.$el = vm.__patch__(prevVnode, vnode); }};// the 12.vm. __patch__ method does the DOM diff comparison and updates the DOM.
Copy the code

After looking at the process of Vue template rendering, we can get the following flow chart:

At this point, we know that Watcher is actually created during the Vue initialization phase, at the beforeMount location in the lifecycle. The Render method is executed when Watcher is created, and finally the Vue code is rendered into the real DOM.

If we integrate the previous process, we can get the following process:

How does the Vue update when the data changes?

When the Data changes, the dep. notify method is called, and the update method inside Watcher is called. This method will queue all the watchers using the Data and start an asynchronous queue for updates. Finally execute the _render method to complete the page update.

The overall process is as follows:

All right, so far, Vue’s responsivity principle has been thoroughly analyzed. If you don’t understand it yet, you might as well look at the figure above in detail.

The component rendering

Originally explore to the above flow chart is over, but CURIOUS I thought of a problem 😂.

How are Vue components rendered?

With this question in mind, I continued to consult the source code.

// Starting from template compilation, the following functions are executed when a custom component is found
// 1. compileToFunctions(template)
// 2. compile(template, options);
// 3. const ast = parse(template.trim(), options)
// 4. const code = generate(ast, options)
// 5. createElement

// 6. createComponent
export function createComponent(
  Ctor: Class<Component> | Function | Object | void, data: ? VNodeData, context: Component, children: ?Array<VNode>, tag? :string
) :VNode | Array<VNode> | void {
  // $options._base is the global Vue constructor defined in initGlobalAPI at initialization: Vue. Options. _base = Vue
  const baseCtor = context.$options._base;
  // Ctor is an object exported under the 
  if (isObject(Ctor)) {
    // Export the object from the component from Vue to get a constructor
    // equivalent to vue.extend (YourComponent)
    Ctor = baseCtor.extend(Ctor);
  }
  const vnode = new VNode(`vue-component-${Ctor.cid}xxx`);
  return vnode;
}

// 7. Implement component inherit Vue and call Vue._init to initialize
Vue.extend = function (extendOptions: Object) :Function {
  const Super = this;
  const Sub = function VueComponent(options) {
    // Call vue.prototype. _init, and the process remains the same as the first load
    this._init(options);
  };
  // Extends Vue (Component extends Vue
  Sub.prototype = Object.create(Super.prototype);
  Sub.prototype.constructor = Sub;
  return Sub;
};
Copy the code

After reading the source code of component rendering, combined with the above, I reorganized a flow chart, the blue part of the figure is the process of rendering components.

Well, now that we’re really done, the final flow chart is the one above.

Question: Now do you understand the Vue responsivity principle?

If you still feel confused, I have prepared a schematic diagram with notes here 😂

Thinking and summarizing

This article introduces the principle of Vue responsiveness from the point of view of the source code. Let’s briefly review it.

  1. Starting from new Vue, the Data changes in Data are monitored through GET and set. Meanwhile, a Dep is created to collect Watcher using this Data.
  2. Compile the template, create the Watcher, and identify dep. target as the current Watcher.
  3. When compiling a template, if you use Data from Data, the Data get method is triggered, and dep.addSub is called to collect Watcher.
  4. When the Data is updated, the Data set method is triggered, and dep. notify is called to notify all watchers using the Data to update the DOM.

Finally, if you have any thoughts on this, feel free to leave a comment!