Vnode virtual DOM of Vue

Vnode virtual DOM of Vue

Why use virtual DOM (Virtual DOM)

  • Manual DOM operation is more troublesome, and browser compatibility issues need to be considered. Although DOM operations such as JQuery are simplified, as the project becomes more complex, the complexity of DOM operations will also increase.
  • In order to simplify the complex operation of the DOM, various MVVM frameworks have emerged. The MVVM framework solves the problem of synchronization of views and states
  • In order to simplify the operation of the view, we can use the template engine, but the template engine does not solve the problem of tracking state changes
  • The advantage of Virtual DOM is that you don t need to update the DOM immediately when the state changes. You only need to create a virtual tree to describe the DOM. Virtual DOM will figure out how to effectively update the DOM.

In the JQuery era, when we render the page, we often manipulate the Dom through js, and then mount the data to the DOM. We know from many front ports or books that manipulating the DOM is very expensive. What affects front-end performance is often not the logic of js, but the operation of updating the DOM. Let's take a look at why manipulating the DOM affects performance? 1. let's try to get the attributes on a div

//DOM <div class="box"></div>
//js const div = document.querySelector('.box') let a = '' for ( const prop in div) { a = a + prop + '' } console .log(a) Copy code

We can look at the densely packed attributes. This is just the attribute of a div. If we manipulate hundreds of DOM elements, on the one hand, we will calculate the DOM with such attributes, and on the other hand, the browser will be triggered by modifying the DOM. The rearrangement and redrawing of the engine further consumes performance. So we have to figure out whether we can map the DOM into objects, mount the processed data to these objects, and then we compare the differences with the previous objects, so that we can only update the ones we need to modify DOM was created, and virtual DOM was born.

Virtual DOM in Vue

Let's first take a look at what Vnode in the source code looks like. We will download the Vue source code from github, in the src/core/vdom/vnode.js file

export default class VNode { tag : string | void ; data: VNodeData | void ; children:? Array <VNode>; text: string | void ; elm: Node | void ; ns: string | void ; context: Component | void ; //rendered in 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 support constructor ( tag?: string, data?: VNodeData, children?:? Array <VNode>, text?: string, elm?: Node, context?: Component, componentOptions?: VNodeComponentOptions, ?: asyncFactory Function ) { /* current node tag name */ the this .tag Tag = /* current node corresponding to the object, comprising a specific number of data, is a type VNodeData, reference data types VNodeData */ this .data = data /*The children of the current node are an array*/ this .children = children /*The text of the current node*/ this .text = text /*The real dom node corresponding to the current virtual node*/ this .elm = elm /*The namespace of the current node*/ this .ns = undefined /*The compilation scope of the current node*/ this .context = context /*Functional component scope*/ this .fnContext = undefined this .fnOptions = undefined this.fnScopeId =undefined /*The key attribute of the node is used as the symbol of the node to optimize */ this .key = data && data.key /*option option of the component*/ this .componentOptions = componentOptions /*the component of the current node corresponding Instance*/ this .componentInstance = undefined /*The parent node of the current node*/ this .parent = undefined /*In short, it is whether it is native HTML or just normal text. InnerHTML is true, and textContent is false*/ this .raw = false /*Whether it is a static node*/ this .isStatic = false /*Whether it is inserted as a follower node*/this .isRootInsert = true /*Whether it is a comment node*/ this .isComment = false /*Whether it is Clone node */ this.isCloned = false /*Is there a v-once instruction*/ this .isOnce = false this .asyncFactory = asyncFactory this .asyncMeta = undefined this .isAsyncPlaceholder = false } //DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ get child (): Component | void { return this .componentInstance } } Copy code

The above are the properties of Vnode. If you first look at it, you may be a little confused. What can it do? Now we use a virtual node to represent the DOM

{ tag : 'div' data : { class : 'v-div' }, children : [ { tag : 'span' , data : { class : 'v-span' } text : 'hello, VNode' } ] } Copy code

After the above Vnode node is rendered, it looks like this

< Div class = "V-div" > < span class = "V-span" > Hello, vnode </span > </div > copy the code

Method of generating Vnode

  • Create an empty Vnode node
export const createEmptyVNode = ( text: string = '' ) => { const node = new VNode() node.text = text node.isComment = true return node } Copy code
  • Create a text node
export function createTextVNode ( val: string | number ) { return new VNode( undefined , undefined , undefined , String (val)) } Copy code
  • Clone a Vnode node
export function cloneVNode ( vnode: VNode ): VNode { const cloned = new VNode( vnode.tag, vnode.data, //#7975 //clone children array to avoid mutating original in case of cloning //a child. 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 code
  • createComponent creates a component node
export function createComponent ( Ctor: Class<Component> | Function | Object | void , data: ?VNodeData, context: Component, children:? Array <VNode>, tag?: string ): VNode | Array < VNode > | void { if (isUndef(Ctor)) { return } const baseCtor = context.$options._base //plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } //if at this stage it's not a constructor or an async component factory, //reject. /*If Ctor is still not a constructor or an asynchronous component factory at this stage, return directly*/ if ( typeof Ctor !== 'function' ) { if (process.env.NODE_ENV !== 'production' ) { warn( `Invalid Component definition: ${ String (Ctor)} ` , context) } return } //async component let asyncFactory if (isUndef(Ctor.cid)) { asyncFactory = Ctor Ctor = resolveAsyncComponent(asyncFactory, baseCtor) if (Ctor === undefined ) { //return a placeholder node for async component, which is rendered //as a comment node but preserves all the raw information for the node. //the information will be used for async server-rendering and hydration. return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } data = data || {} //resolve constructor options in case global mixins are applied after //component constructor creation resolveConstructorOptions(Ctor) //transform component v-model data into props & events if (isDef(data.model)) { transformModel(Ctor.options, data) } //extract props const propsData = extractPropsFromVNodeData(data, Ctor, tag) //functional component if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children) } //extract listeners, since these needs to be treated as //child component listeners instead of DOM listeners const listeners = data.on //replace with listeners with .native modifier //so it gets processed during parent component patch. data.on = data.nativeOn if (isTrue(Ctor.options.abstract)) { //abstract components do not keep anything //other than props & listeners & slot //work around flow const slot = data.slot data = {} if (slot) { data.slot = slot } } //install component management hooks onto the placeholder node installComponentHooks(data) //return a placeholder vnode const name = Ctor.options.name || tag const vnode = new VNode( `vue-component- ${Ctor.cid} ${name? `- ${name} ` : '' } ` , data, undefined , undefined , undefined , context, {Ctor, propsData, listeners, tag, children }, asyncFactory ) //Weex specific: invoke recycle-list optimized @render function for //extracting cell-slot template. //https://github.com/Hanks10100/weex-native-directive/tree/master/component /* istanbul ignore if */ if (__WEEX__ && isRecyclableComponent(vnode)) { return renderRecyclableComponentTemplate(vnode) } return vnode } Copy code
  • createElement
export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array < VNode > { /*Compatible with the case of not transmitting data*/ if ( Array .isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } /*If alwaysNormalize is true, the normalizationType is marked as ALWAYS_NORMALIZE*/ if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } /*Create a virtual node*/ return _createElement(context, tag, data, children, normalizationType) } /*Create VNode node*/ export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object , data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array < VNode > { /* If data is undefined (undefined or null) or __ob__ of data has been defined (representing it has been observed, the Oberver object is bound to it), https://cn.vuejs.org/v2/guide/render-function.html#Constraints Then create an empty node */ if (isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV !== 'production' && 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() } //object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } /*If the tag does not exist, create an empty node*/ if (!tag) { //in case of component :is set to falsy value return createEmptyVNode() } //warn against non-primitive key if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { if (!__WEEX__ || !( '@binding' in data.key)) { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.' , context ) } } //support single function children as default scoped slot /*default scoped slot*/ if ( Array .isArray(children) && typeof children[ 0 ] === 'function' ) { data = data || {} data.scopedSlots = { default : children[ 0 ]} children.length = 0 } if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns if ( typeof tag === 'string' ) { let Ctor /*Get tag namespace*/ ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) /*Determine whether it is a reserved tag*/ if (config.isReservedTag(tag)) { //platform built-in elements if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data. nativeOn)) { warn( `The .native modifier for v-on is only valid on components but it was used on < ${tag} >.` , context ) } /*If it is a reserved tag, create a corresponding node*/ vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined , undefined , context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components' , tag))) { //component /*Find this from the components of the option of the vm instance tag, if it exists, it is a component, create the corresponding node, Ctor is the component's construction class*/ vnode = createComponent(Ctor, data, context, children, tag) } The else { //Unknown or Unlisted namespaced Elements //Check On May GET IT AT Runtime Because Assigned A namespace When ITS //parent normalizes Children /* unknown elements, checked at run time, as parent component assembly facilitator sequences may When assigning a namespace */ vnode = new VNode( tag, data, children, undefined , undefined , context ) } } else { //direct component options/constructor /* When tag is not a string, it is the component's constructor class*/ vnode = createComponent(tag, data, context, children) } if ( Array .isArray(vnode)) { return vnode } else if (isDef(vnode)) { /*If there is a namespace, recursively apply the namespace to all child nodes*/ if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings( data) return vnode } else { /*If the vnode is not successfully created, create an empty node*/ return createEmptyVNode() } } Copy code
Copy code