Following on from the previous section, we took a brief look at the complex mount process of Vue through graphical process, code analysis, and finally the general process of template compilation. At the heart of the mount, however, we did not analyze how the template’s rendering function is translated into a visual DOM node after it is compiled. So in this section, we’ll return to the last step in Vue instance mounting: rendering DOM nodes. In rendering the real DOM, Vue introduces the concept of the virtual DOM, another important concept in Vue architectural design. As a buffer layer between THE JS object and the real DOM, the virtual DOM can alleviate the performance problems caused by frequent DOM manipulation by JS.

4.1 Virtual DOM

4.1.1 Browser rendering process

When the browser receives an Html file, the JS engine and the browser’s rendering engine start working. From the rendering engine’s point of view, it first parses the HTML file into a DOM tree, and at the same time, the browser recognizes and loads the CSS styles and merges them with the DOM tree into a render tree. Once you have a rendering tree, the rendering engine calculates the location of all the elements and prints the final content on the screen by drawing. JS engine and rendering engine are two independent threads, but it can trigger JS engine rendering engine work, when we through script to modify element position or appearance, JS engine will use the DOM related API methods to operate the DOM object, the rendering engine to start working, rendering engine will trigger reflux or redrawn. Here are two concepts for reflux repainting:

  • Backflow: When we are onDOMWhen a change in the size of an element is caused by a change in the size of an element, the browser needs to recalculate the size and position of the element and eventually draw the recalculated result, a process known as backflow.
  • Redraw: When we’re onDOMThe browser does not need to recalculate the size and position of the element, but simply redraw the new style. This process is called redrawing.

Refluxing is obviously more performance expensive than painting.

Given the basic rendering mechanism of the browser, it is easy to imagine that when constantly modifying the DOM through JS, it inadvertently triggers backflow or redraw to the rendering engine, which has a huge performance cost. So to keep overhead down, what we need to do is minimize DOM manipulation. Is there any way to do that?

4.1.2 Buffer Layer – Virtual DOM

The virtual DOM was created to address the performance problems associated with frequent DOM manipulation. Virtual DOM(hereinafter called Virtual DOM) abstracts the state of the page into the form of JS objects. Essentially, it is the middle layer between JS and the real DOM. When we want to use JS scripts to conduct DOM operations in large quantities, we will give priority to the Virtual DOM JS object. Finally, notify and update to the real DOM by comparing the parts that will be changed. Although the real DOM is ultimately manipulated, the Virtual DOM can consolidate multiple changes into a single batch operation, reducing the number of DOM rearrangements, which in turn reduces the time it takes to generate render trees and draw.

Let’s look at what a real DOM contains:

DOM
DOM
DOM
DOM
Virtual DOM
DOM
DOM

/ / true DOM
<div id="real"><span>dom</span></div>

// The JS object corresponding to the real DOM
{
    tag: 'div'.data: {
        id: 'real'
    },
    children: [{
        tag: 'span'.children: 'dom'}}]Copy the code

4.2 Vnode

Vue also introduces the concept of virtual DOM in the optimization of rendering mechanism. It uses the Vnode constructor to describe a DOM node.

4.2.1 Vnode constructor

var VNode = function VNode (tag,data,children,text,elm,context,componentOptions,asyncFactory) {
    this.tag = tag; / / label
    this.data = data;  / / data
    this.children = children; / / child nodes
    this.text = text; ......};Copy the code

There are almost 20 attributes defined by Vnode. Obviously, using Vnode objects is much simpler than real DOM objects. It only describes key attributes of nodes, such as label names, data, child nodes, etc. The browser-specific DOM methods are not preserved. In addition, Vnode also has other attributes that extend the flexibility of Vue.

The source code also defines methods for creating vNodes.

4.2.2 Creating a Vnode Comment Node

// Create a comment vnode node
var createEmptyVNode = function (text) {
    if ( text === void 0 ) text = ' ';

    var node = new VNode();
    node.text = text;
    node.isComment = true; // mark the comment node
    return node
};
Copy the code

4.2.3 Creating a Vnode Text Node

// Create a text vnode
function createTextVNode (val) {
    return new VNode(undefined.undefined.undefined.String(val))
}
Copy the code

4.2.4 cloning vnode

function cloneVNode (vnode) {
    var cloned = new VNode(
      vnode.tag,
      vnode.data,
      vnode.children && vnode.children.slice(),
      vnode.text,
      vnode.elm,
      vnode.context,
      vnode.componentOptions,
      vnode.asyncFactory
    );
    cloned.ns = vnode.ns;
    cloned.isStatic = vnode.isStatic;
    cloned.key = vnode.key;
    cloned.isComment = vnode.isComment;
    cloned.fnContext = vnode.fnContext;
    cloned.fnOptions = vnode.fnOptions;
    cloned.fnScopeId = vnode.fnScopeId;
    cloned.asyncMeta = vnode.asyncMeta;
    cloned.isCloned = true;
    return cloned
  }
Copy the code

Note:cloneVnoderightVnodeIs only a shallow copy, it does not clone child nodes deeply.

4.3 Creating a Virtual DOM

A quick review of the mounting process is performed by calling the $mount method on the Vue instance, the core of which is the mountComponent function. If we pass a template template, the template will be parsed by the compiler and eventually generated for each platform, with the render function wrapped around the with statement. If you pass the render function, you skip the template compilation and go straight to the next stage. The next stage is to take the render function, call vm._render() to convert the render function into the Virtual DOM, and finally render the Virtual DOM into a real DOM node using the vm._update() method.

Vue.prototype.$mount = function(el, hydrating) {...return mountComponent(this, el)
}
function mountComponent() {... updateComponent =function () {
        vm._update(vm._render(), hydrating);
    };
}

Copy the code

Let’s first look at how the vm._render() method converts the render function into a Virtual DOM.

Recall from the first chapter that Vue defines a number of properties and methods as code is introduced. There is a renderMixin procedure that we only mentioned before defines functions related to rendering. In fact, it defines only two important methods, the _render function being one of them.

// When Vue is introduced, the renderMixin method is executed. This method defines several methods on the Vue prototype, one of which is the _render function
renderMixin();//
function renderMixin() {
    Vue.prototype._render = function() {
        var ref = vm.$options;
        varrender = ref.render; ...try {
            vnode = render.call(vm._renderProxy, vm.$createElement);
        } catch(e) {···} ···return vnode
    }
}
Copy the code

Render. Call (vm._renderProxy, vm.$createElement); render. It also binds the this pointer to the render function execution. The vm.$createElement method is passed in as an argument to the Render function. Recall that when writing the render function, we wrote the render function with createElement, the first argument to the render function, which is the defined $createElement method.

new Vue({
    el: '#app'.render: function(createElement) {
        return createElement('div', {}, this.message)
    },
    data() {
        return {
            message: 'dom'}}})Copy the code

When _init is initialized, there is an initRender function that defines the render function method, including the vm.$createElement method. In addition to $createElement, the _c method is defined similarly. Vm. _c is the function called when the template is internally compiled into the render function, and vm.$createElement is the function called when the render function is hand-written. The only difference between the two is the last parameter. The template-generated render method ensures that the children are vNodes, whereas hand-written render requires some verification and conversion.

function initRender(vm) {
    vm._c = function(a, b, c, d) { return createElement(vm, a, b, c, d, false); }
    vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
}
Copy the code

The createElement method is essentially a wrapper around the _createElement method. Before calling _createElement, it will process the parameters passed to it, since the hand-written render function has different parameters. Take a simple example.

/ / no data
new Vue({
    el: '#app'.render: function(createElement) {
        return createElement('div'.this.message)
    },
    data() {
        return {
            message: 'dom'}}})// 有data
new Vue({
    el: '#app'.render: function(createElement) {
        return createElement('div', {}, this.message)
    },
    data() {
        return {
            message: 'dom'}}})Copy the code

If the second argument is a variable or array, data is not passed by default, because data is usually an object.

function createElement (
    context, //Vm instances tag,//The tag data,//Node related data, attribute children,//Child nodes normalizationType, alwaysNormalize//Distinguish between internally compiled render and handwritten render) {
    If there is no data, the third parameter is used as the fourth parameter, and so on.
    if (Array.isArray(data) || isPrimitive(data)) {
      normalizationType = children;
      children = data;
      data = undefined;
    }
    // alwaysNormalize distinguishes between internal compilation and user handwritten render
    if (isTrue(alwaysNormalize)) {
      normalizationType = ALWAYS_NORMALIZE;
    }
    return _createElement(context, tag, data, children, normalizationType) // How to actually generate a Vnode
  }
Copy the code

4.3.1 Data specification detection

Vue is exposed to the user’s hand rendering of the template using the render function. Therefore, _createElement will check the normalization of the data before creating a Vnode to expose the user to illegal data type errors. Here are a few common mistakes that can be made in a real world scenario to help us understand how to deal with them in the source code.

  1. Do it with reactive objectsdataattribute
new Vue({
    el: '#app'.render: function (createElement, context) {
       return createElement('div'.this.observeData, this.show)
    },
    data() {
        return {
            show: 'dom'.observeData: {
                attr: {
                    id: 'test'}}}}})Copy the code
  1. When the value of the special attribute key is a non-string, non-numeric type
new Vue({
    el: '#app'.render: function(createElement) {
        return createElement('div', { key: this.lists }, this.lists.map(l= > {
           return createElement('span', l.name)
        }))
    },
    data() {
        return {
            lists: [{
              name: '111'
            },
            {
              name: '222'}],}}})Copy the code

These specifications are detected and reported before the Vnode is created. The source code is as follows:

function _createElement (context,tag,data,children,normalizationType) {
    // 1. Data objects cannot be reactive data defined in the Vue data attribute.
    if (isDef(data) && isDef((data).__ob__)) {
      warn(
        "Avoid using observed data object as vnode data: " + (JSON.stringify(data)) + "\n" +
        'Always create fresh vnode data objects in each render! ',
        context
      );
      return createEmptyVNode() // Returns the comment node
    }
    if (isDef(data) && isDef(data.is)) {
      tag = data.is;
    }
    if(! tag) {// Prevent dynamic components: special treatment is required when the is property is set to false
      return createEmptyVNode()
    }
    // 2. The key value can only be string or number
    if(isDef(data) && isDef(data.key) && ! isPrimitive(data.key) ) { { warn('Avoid using non-primitive value as key, ' +
          'use string/number value instead.', context ); ...}}}Copy the code

These normative tests ensure the complete generation of subsequent Virtual DOM trees.

4.3.2 Children normalization

Virtual DOM Tree is a Virtual DOM tree composed of each Vnode in tree form. What we need when converting real nodes is such a complete Virtual DOM tree. Therefore, we need to ensure that each child node is Vnode type.

  • Template compilationrenderStudent: Function, theoreticallytemplateTemplates are generated by compilationrenderFunctions areVnodeType, with the exception that a functional component returns an array (see the functional component article for this special example)VueThe processing is to put the wholechildrenFlatten into a one-dimensional array.
  • User definedrenderThe delta function, in this case, is divided into two cases, one is whenchidrenFor text nodes, this time through the previous introductioncreateTextVNodeTo create a text nodeVNode; The other is more complicated whenchildrenThere arev-forWhen a nested array appears, the processing logic is traversalchildrenIf it is still an array, the recursive call continues until the underlying type is calledcreateTextVnodeMethod transformation toVnode. So if I recurse,childrenIt also becomes a typeVnodeThe array.
function _createElement() {...if (normalizationType === ALWAYS_NORMALIZE) {
      // User defined render function
      children = normalizeChildren(children);
    } else if (normalizationType === SIMPLE_NORMALIZE) {
      // The render function generated by the template compilationchildren = simpleNormalizeChildren(children); }}// Handle the compile-generated render function
function simpleNormalizeChildren (children) {
    for (var i = 0; i < children.length; i++) {
        // If the child node is an array, perform the flattening operation to create a one-dimensional array.
        if (Array.isArray(children[i])) {
        return Array.prototype.concat.apply([], children)
        }
    }
    return children
}

// Handle the user-defined render function
function normalizeChildren (children) {
    // Call recursively until the child node is the base type, then call to create the text node Vnode
    return isPrimitive(children)
      ? [createTextVNode(children)]
      : Array.isArray(children)
        ? normalizeArrayChildren(children)
        : undefined
  }

// Check whether it is a base type
function isPrimitive (value) {
    return (
      typeof value === 'string' ||
      typeof value === 'number' ||
      typeof value === 'symbol' ||
      typeof value === 'boolean')}Copy the code

4.3.4 Actual Scenario

After data detection and component normalization, a complete VNode tree can be generated by using new VNode(). Note that sub-components will be encountered in the _render process, which will give priority to the initialization of sub-components, which will be analyzed in the component section. Let’s finish our analysis of the Render function to the Virtual DOM with a practical example.

  • templateTemplate form
var vm = new Vue({
  el: '#app'.template: '<div><span>virtual dom</span></div>'
})
Copy the code
  • Template compilation generationrenderfunction
(function() {
  with(this) {return _c('div',[_c('span',[_v("virual dom")]])}})Copy the code
  • Virtual DOM tree(omitted version)
{
  tag: 'div'.children: [{
    tag: 'span'.children: [{
      tag: undefined.text: 'virtual dom'}}}]]Copy the code

4.4 Mapping virtual VNodes to real DOM

Back to the last updateComponent process, after the virtual DOM is generated by the virtual DOM tree, the _update method on the Vue prototype is called to map the virtual DOM to the real DOM. _update can be called at two times, one during the initial render phase and the other during the data update phase.

updateComponent = function () {
    // render generates the virtual DOM, update renders the real DOM
    vm._update(vm._render(), hydrating);
};
Copy the code

The vm._update method is defined in lifecycleMixin.

lifecycleMixin()
function lifecycleMixin() {
    Vue.prototype._update = function (vnode, hydrating) {
        var vm = this;
        var prevEl = vm.$el;
        var prevVnode = vm._vnode; // prevVnode is the old vNode node
        // Check whether there are old nodes to determine whether it is the first rendering or data update
        if(! prevVnode) {// First render
            vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)}else {
            // Data updatevm.$el = vm.__patch__(prevVnode, vnode); }}Copy the code

At the heart of _update is the __patch__ method, which is an empty function for server rendering because there is no DOM. In browsers with DOM objects, __patch__ is a reference to the patch function.

// Patch is an empty function Vue. Prototype. __patch__ =inBrowser ? patch : noop;
Copy the code

The Patch method, in turn, returns the createPatchFunction method, which passes an object as a parameter and has two properties, nodeOps and Modules. NodeOps encapsulates a set of methods for manipulating native DOM objects. Modules defines the module’s hook functions.

 var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });

// Freeze a collection of methods that manipulate dom objects
 var nodeOps = /*#__PURE__*/Object.freeze({
    createElement: createElement$1.createElementNS: createElementNS,
    createTextNode: createTextNode,
    createComment: createComment,
    insertBefore: insertBefore,
    removeChild: removeChild,
    appendChild: appendChild,
    parentNode: parentNode,
    nextSibling: nextSibling,
    tagName: tagName,
    setTextContent: setTextContent,
    setStyleScope: setStyleScope
  });

// Defines the module's hook function
  var platformModules = [
    attrs,
    klass,
    events,
    domProps,
    style,
    transition
  ];

var modules = platformModules.concat(baseModules);
Copy the code

The actual createPatchFunction is over a thousand lines of code, but it starts with a series of auxiliary methods. The core of the createPatchFunction is dom manipulation by calling the createElm method, creating nodes, inserting child nodes, Recursively creates a complete DOM tree and inserts it into the Body. In addition, diff algorithm will be used to judge the difference between vNodes before and after the generation of real stages in order to minimize the change of real stages. There will be a section on the diff algorithm later. The createPatchFunction process just needs to remember a few conclusions. The function calls the wrapped DOM API internally to generate real nodes based on the results of the Virtual DOM. When a component Vnode is encountered, the mount process of the sub-component is recursively called, which will also be analyzed in the following sections.

4.5 summary

This section examines the two core methods of the mountComponent, Render and Update, before focusing on the bridge that exists between JS operations and DOM rendering: the Virtual DOM. JS batch operations on DOM nodes will directly reflect the Virtual DOM, the description object, and the final result will be directly applied to the real node. It can be said that Virtual DOM greatly improves rendering performance. This paper focuses on the render function to transform into a Virtual DOM process, and describes the implementation of _update function. In fact, both of these processes involve components, so this section will not be able to go into much depth. The next section will start with components. I believe that after analyzing the components, the reader will have a deeper understanding and reflection on the entire rendering process.


  • An in-depth analysis of Vue source code – option merge (1)
  • An in-depth analysis of Vue source code – option merge (2)
  • In-depth analysis of Vue source code – data agents, associated child and parent components
  • In-depth analysis of Vue source code – instance mount, compile process
  • In-depth analysis of Vue source code – complete rendering process
  • In-depth analysis of Vue source code – component foundation
  • In-depth analysis of Vue source code – components advanced
  • An in-depth analysis of Vue source code – Responsive System Building (PART 1)
  • In – Depth Analysis of Vue source code – Responsive System Building (Middle)
  • An in-depth analysis of Vue source code – Responsive System Building (Part 2)
  • In-depth analysis of Vue source code – to implement diff algorithm with me!
  • In-depth analysis of Vue source code – reveal Vue event mechanism
  • In-depth analysis of Vue source code – Vue slot, you want to know all here!
  • In-depth analysis of Vue source code – Do you understand the SYNTAX of V-Model sugar?
  • In-depth analysis of Vue source – Vue dynamic component concept, you will be confused?
  • Thoroughly understand the keep-alive magic in Vue (part 1)
  • Thoroughly understand the keep-alive magic in Vue (2)