Following the previous article, we looked at the process of data initialization in detail, and also looked at several steps of data update. Now enter the detailed update process, which involves the virtual DOM and the patch algorithm for updating DOM operations.

Virtual DOM

In modern UI architecture design (collectively referred to as MV* framework, V is the modern markup language), data-driven has become a core element. The introduction of virtual DOM is a data-driven implementation. Virtual DOM(Virtual DOM) is a JS abstract representation of THE DOM. They are JS objects that describe the DOM structure and relationships. Various state changes of the application will act on the virtual DOM and eventually map to the DOM. The working flow chart is as follows:

advantages

  1. The virtual DOM is lightweight and fast: When data changes, the virtual DOM changes. Comparing the old virtual DOM with the new one minimizes DOM operations (real DOM operations are expensive), improving performance and user experience.
  2. Cross-platform: Convert virtual DOM updates into different runtime specific operations to achieve cross-platform implementation (the source structure of Vue distinguishes Web Platform from Platform Platform).
  3. Compatibility: You can also add compatibility code to enhance the compatibility of operations.

The need for Vue to introduce the virtual DOM

Vue 1.x has fine-grained data change detection that does not require the virtual DOM, but this fine-grained approach incurs a lot of overhead that is unacceptable for large projects. Therefore, Vue 2.x opted for a medium-grained solution, one Watcher instance per component, so that only components are notified of state changes, and virtual DOM is introduced for comparison and rendering.

It can be said that the introduction of virtual DOM in Ve2. X is inevitable. The design structure changed: there was a one-to-one correspondence between the components and the Watcher. This requires the introduction of the virtual DOM to cope with this change.

The source code

Let’s take a look at what the virtual DOM in ve2. X looks like. It’s called VNode:

exportdefault class VNode { tag: string | void; data: VNodeData | void; children: ? Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; // renderedin this component's scope key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node // strictly internal raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function asyncMeta: Object | void; isAsyncPlaceholder: boolean; ssrContext: Object | void; fnContext: Component | void; // real context vm for functional nodes fnOptions: ? ComponentOptions; // for SSR caching devtoolsMeta: ? Object; // used to store functional render context for devtools fnScopeId: ? string; // functional scope id supportCopy the code

There are a lot of variables. Array

= Array

How did this come about? Let’s go back to the $mount process in the source code and find a starting point.

From the core/instance/lifecycle. The js mountComponent () :

export functionmountComponent ( vm: Component, el: ? Element, hydrating? : boolean ): Component { ... / / to omitletUpdateComponent = () => {// Updates the Component definition to do two things: Vm._update (vm._render(), hydrating)} new Watcher(vm, updateComponent, noop, {render(vDOM), vm._update(vm._render(), hydrating)} new Watcher(vm, noop, {before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true/* isRenderWatcher */) ... / / omit}Copy the code

You see that in the New Watcher instance, the association is created with updateComponent. Focus on vm._render().

In the core/instance/render. In js:

import { createElement } from '.. /vdom/create-element'

export functioninitRender (vm: Component) { ... _c = (a, b, c, d) => createElement(vm, a, b, c, d);false// User written render, typical Currified VM.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)... // omit} vue.prototype. _render =function(): VNode { const vm: Component = this ... Const {render, _parentVnode} = vm.$options// The final calculated virtual DOMletVnode // executes the render function, passing in the argument is$createElement(H argument in common render() method)let vnode = render.call(vm._renderProxy, vm.$createElement)... / / to omitreturn vnode
  }
Copy the code

See the VNode. This process executes the Render function, using the createElement() method. It seems that the core of creating a VNode is in this method. Associated with core/vdom/create-element.js:

// Return vNodes or an array of vNodesexport functioncreateElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> { ... / / to omitreturn _createElement(context, tag, data, children, normalizationType)
}

export function_createElement ( context: Component, tag? : string | Class<Component> | Function | Object, data? : VNodeData, children? : any, normalizationType? : number ): VNode | Array<VNode> { ... // omit // core: VNode generation process // The tag passed in May be native HTML tags, or may be user-defined tagslet vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnodeNs) | | config. GetTagNamespace (tag) / / is native to retain tag, create VNode directlyif (config.isReservedTag(tag)) {
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    }else if((! data||! data.pre)&&isDef(Ctor=resolveAsset(context.$options.'components'VNode = createComponent(Ctor, data, context, children, tag)}else {
      //
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else{ // direct component options / constructor vnode = createComponent(tag, data, context, children) } ... / / omit}Copy the code

summary

As we string the process together, we see that the Render function processes the different parameter types passed in by calling createElement() to get the VNode tree. The flow chart is as follows:

So how do you compare the changes between the old and new VNodes to perform updates to the real DOM with minimal cost? The patch algorithm will be discussed in the next article.


Vue source code interpretation

(1) : Vue constructor and initialization process

(II) : Data response and implementation

(3) : array responsive processing

(iv) Vue’s asynchronous update queue

(5) The introduction of virtual DOM

(VI) Data update algorithm — Patch algorithm

(7) : realization of componentization mechanism

(8) : computing properties and listening properties

The Optimize stage of the compilation process

Vue

Vue’s error handling mechanism

Parse the working process of Vue in handwritten code

Vue Router handwriting implementation