1, What is virtual DOM
Virtual DOM (virtual DOM) ) I believe everyone is familiar with this concept React reach Vue , fictitious DOM It brings cross platform capability (react native) to both frameworks and Weex)
In fact, it's just a layer of abstraction from the real DOM, using JavaScript Object (VNode) Node) as the basic tree, the node is described by the attributes of the object. Finally, the tree can be mapped to the real environment through a series of operations
In a Javascript object, the virtual DOM As a Object object. It contains at least three attributes: tag name (tag), attribute (attrs) and child element object (children). The names of these three attributes may be different in different frameworks
The purpose of creating virtual DOM is to better render the virtual nodes into the page view, so the nodes of the virtual DOM object and the properties of the real DOM take care of each other
Virtual DOM technology is also used in vue
Define real DOM
<div id="app"> <p class="p">Node content</p> <h3>{{ foo }}</h3> </div>
Instantiate vue
const app = new Vue({ el:"#app", data:{ foo:"foo" } })
By observing the render of render, we can get the virtual DOM
(function anonymous() { with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',{staticClass:"p"}, [_v("Node content")]),_v(" "),_c('h3',[_v(_s(foo))])])}})
Through VNode, vue can create nodes, delete nodes and modify nodes for this abstract tree. Some minimum units to be modified are obtained through diff algorithm, and then the view is updated, which reduces dom operations and improves performance
2, Why do I need virtual DOM
DOM is very slow, its elements are very large, and most of the performance problems of pages are caused by Dom operations
A real DOM node, even the simplest div, contains many attributes. You can print it and feel it intuitively:
It can be seen that the cost of operating DOM is still expensive. Frequent operations will still cause page jams, affecting the user experience
for instance:
When you use the traditional native api or jQuery to operate the DOM, the browser will execute the process from beginning to end from building the DOM tree
When you need to update 10 DOM nodes in one operation, the browser is not so intelligent. After receiving the first DOM update request, you don't know that there are 9 subsequent update operations, so you will execute the process immediately and finally execute the process 10 times
Through VNode, 10 DOM nodes are also updated. Instead of operating the DOM immediately, the virtual DOM saves the diff content updated 10 times to a local js object, and finally attach es the js object to the DOM tree at one time to avoid a lot of unnecessary calculations
Many people think that the biggest advantage of virtual DOM is diff algorithm, which reduces the performance consumption caused by JavaScript operating real dom. Although this virtual DOM brings one advantage, it is not all. The biggest advantage of virtual DOM is that it abstracts the original rendering process and realizes cross platform capability, not limited to browser DOM, but Android and IOS The native components of can be recent hot applets or various GUI s
3, How to implement virtual DOM
First, let's look at the structure of VNode in vue
Source location: src/core/vdom/vnode.js
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 functionalContext: Component | void; // only for functional component root nodes key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node 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? constructor ( tag?: string, data?: VNodeData, children?: ?Array<VNode>, text?: string, elm?: Node, context?: Component, componentOptions?: VNodeComponentOptions ) { /*Label name of the current node*/ this.tag = tag /*The object corresponding to the current node contains some specific data information. It is a VNodeData type. You can refer to the data information in VNodeData type*/ this.data = data /*The child node of the current node is 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 /*Compile scope*/ this.context = context /*Functional component scope*/ this.functionalContext = undefined /*The key attribute of the node is used as the flag of the node for optimization*/ this.key = data && data.key /*option options for components*/ this.componentOptions = componentOptions /*The instance of the component corresponding to the current node*/ this.componentInstance = undefined /*Parent node of the current node*/ this.parent = undefined /*In short, it is whether it is native HTML or just plain text. innerHTML is true and textContent is false*/ this.raw = false /*Static node flag*/ this.isStatic = false /*Insert as follow node*/ this.isRootInsert = true /*Is it a comment node*/ this.isComment = false /*Is it a clone node*/ this.isCloned = false /*Is there a v-once instruction*/ this.isOnce = false } // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next https://github.com/answershuto/learnVue*/ get child (): Component | void { return this.componentInstance } }
Here is a brief description of VNode:
-
Of all objects context Options all point to Vue example
-
elm Property points to its corresponding real DOM node
vue generates VNode through createElement
Source location: src/core/vdom/create-element.js
export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> { if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } return _createElement(context, tag, data, children, normalizationType) }
You can see createElement above The method is actually right _ createElement Method to judge the input of parameters
export function _createElement( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { 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 (!tag) { // in case of component :is set to falsy value return createEmptyVNode() } ... // support single function children as 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 ( === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } // Create VNode ... }
You can see that _createelementreceives five parameters:
-
context express VNode The context is Component type
-
Tag represents a tag. It can be a string or a string Component
-
data express VNode Data, it is a VNodeData type
-
children Represents the current The child node of VNode, which is of any type
-
normalizationType It represents the type of child node specification. Different types of specifications have different methods, mainly for reference render Is the function compiled or written by the user
According to normalization type children have different definitions for their types
if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if ( === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) }
The simpleNormalizeChildren method call scenario is render Functions are compiled
normalizeChildren method call scenarios are divided into the following two types:
-
render The function is handwritten by the user
-
compile slot,v-for Nested arrays will be generated when
Both simpleNormalizeChildren and normalizeChildren are used to standardize children Becomes a type VNode of Array), I won't expand here
The source code of standardized children is located at src/core/vdom/helpers/normalzie-children.js
After normalizing children, create VNode
let vnode, ns // Judge tag if (typeof tag === 'string') { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { // If it is a built-in node, create a normal VNode directly vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // component // If it is a component type, the VNode node will be created through createComponent 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) }
createComponent is also used to create VNode
Source location: src/core/vdom/create-component.js
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 } // Build subclass constructor 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 (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, context) if (Ctor === undefined) { 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)) { const slot = data.slot data = {} if (slot) { data.slot = slot } } // Install the component hook function and merge the hook function into data.hook installComponentHooks(data) //Instantiating a VNode returns that the VNode of the component does not have children 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 ) if (__WEEX__ && isRecyclableComponent(vnode)) { return renderRecyclableComponentTemplate(vnode) } return vnode }
Let's briefly mention the three key processes for creating VNode from createComponent:
-
Construct subclass constructor Ctor
-
installComponentHooks install component hook function
-
instantiation vnode
Summary
createElement establish VNode The process of each VNode have children,children Each element is also a vnode, which forms a virtual tree structure to describe the real DOM tree structure