认识
一、认识
Virtual DOM
就是用一个原生的 JS
对象去描述一个 DOM
节点,所以它比创建一个 DOM
的代价要小很多。在 Vue.js
中,Virtual DOM
是用 VNode
这么一个 Class
去描述。
VNode
生成过程如下:
-
组件实例初始化,最后执行
$mount
进入挂载阶段-
如果是只包含运行时的
vue.js
,只直接进入挂载阶段,因为这时候的组件已经变成了渲染函数,编译过程通过模块打包器 +vue-loader
+vue-template-compiler
完成的 -
如果没有使用预编译,则必须使用全量的
vue.js
-
-
挂载时如果发现组件配置项上没有
render
选项,则进入编译阶段 -
将模版字符串编译成
AST
语法树,其实就是一个普通的JS
对象 -
然后优化
AST
,遍历AST
对象,标记每一个节点是否为静态静态;然后再进一步标记出静态根节点,在组件后续更新时会跳过这些静态节点的更新,以提高性能 -
接下来从
AST
生成渲染函数,生成的渲染函数有两部分组成:-
负责生成动态节点
VNode
的render
函数 -
还有一个
staticRenderFns
数组,里面每一个元素都是一个生成静态节点VNode
的函数,这些函数会作为render
函数的组成部分,负责生成静态节点的VNode
-
-
接下来将渲染函数放到组件的配置对象上,进入挂载阶段,即执行
mountComponent
方法 -
最终负责渲染组件和更新组件的是一个叫
updateComponent
方法,该方法每次执行前首先需要执行vm._render
函数,该函数负责执行编译器生成的render
,得到组件的VNode
-
将一个组件生成
VNode
的具体工作是由render
函数中的_c
、_o
、_l
、_m
等方法完成的,这些方法都被挂载到Vue
实例上面,负责在运行时设置组件配置信息,然后通过new VNode(组件信息)
生成组件的VNode
-
_c
负责生成组件或HTML
元素的VNode
,_c
是所有render helper
方法中最复杂,也是最核心的一个方法,其它的_xx
都是它的组成部分-
接收标签、属性
JSON
字符串、子节点数组、节点规范化类型作为参数 -
如果标签是平台保留标签或者一个未知的元素,则直接
new VNode(标签信息)
得到VNode
-
如果标签是一个组件,则执行
createComponent
方法生成VNode
-
函数式组件执行自己的
render
函数生成VNode
-
普通组件则实例化一个
VNode
,并且在在data.hook
对象上设置4
个方法,在组件的patch
阶段会被调用,从而进入子组件的实例化、挂载阶段,然后进行编译生成渲染函数,直至完成渲染 -
当然生成
VNode
之前会进行一些配置处理比如:-
子组件选项合并,合并全局配置项到组件配置项上
-
处理自定义组件的
v-model
-
处理组件的
props
,提取组件的props
数据,以组件的props
配置中的属性为key
,父组件中对应的数据为value
生成一个propsData
对象;当组件更新时生成新的VNode
,又会进行这一步,这就是props
响应式的原理 -
处理其它数据,比如监听器
-
安装内置的
init
、prepatch
、insert
、destroy
钩子到data.hooks
对象上,组件patch
阶段会用到这些钩子方法
-
-
-
-
_l
运行时渲染v-for
列表的帮助函数,循环遍历val
值,依次为每一项执行render
方法生成VNode
,最终返回一个VNode
数组 -
_m
,负责生成静态节点的VNode
,即执行staticRenderFns
数组中指定下标的函数
二、结构
tag?: string
data: VNodeData | undefined
children?: Array<VNode> | null
text?: string
elm: Node | undefined
ns?: string
context?: Component // rendered in this component's scope
key: string | number | undefined
componentOptions?: VNodeComponentOptions
componentInstance?: Component // component instance
parent: VNode | undefined | null // 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 // async component factory function
asyncMeta: Object | void
isAsyncPlaceholder: boolean
ssrContext?: Object | void
fnContext: Component | void // real context vm for functional nodes
fnOptions?: ComponentOptions | null // for SSR caching
devtoolsMeta?: Object | null // used to store functional render context for devtools
fnScopeId?: string | null // functional scope id support
isComponentRootElement?: boolean | null // for SSR directives
三、实践
3.1 template
template
模版如下:
<div id="app">
<div> 我是 静态节点 001 </div>
<div>{{ text }}</div>
<div :class="vBind"> v-bind 指令</div>
<div v-if="vIf"> v-if 指令 </div>
<div v-show="vShow"> v-show 指令 </div>
<button @click="handleClick">按钮</button>
<input type="text" v-model="vModel">
<ul>
<li v-for="(item,index) in vFor" :key="index">{{ item.value }}</li>
</ul>
<MyComponent :componentProp="componentProp"></MyComponent>
</div>
3.2 render
生成的 vm.$options.render
函数如下:
(function anonymous() {
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('div', [_v(" 我是 静态节点 001 ")]), _v(" "), _c('div', [_v(_s(text))]), _v(" "), _c('div', {
class: vBind
}, [_v(" v-bind 指令")]), _v(" "), (vIf) ? _c('div', [_v(" v-if 指令 ")]) : _e(), _v(" "), _c('div', {
directives: [{
name: "show",
rawName: "v-show",
value: (vShow),
expression: "vShow"
}]
}, [_v(" v-show 指令 ")]), _v(" "), _c('button', {
on: {
"click": handleClick
}
}, [_v("按钮")]), _v(" "), _c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (vModel),
expression: "vModel"
}],
attrs: {
"type": "text"
},
domProps: {
"value": (vModel)
},
on: {
"input": function($event) {
if ($event.target.composing)
return;
vModel = $event.target.value
}
}
}), _v(" "), _c('ul', _l((vFor), function(item, index) {
return _c('li', {
key: index
}, [_v(_s(item.value))])
}), 0), _v(" "), _c('mycomponent', {
attrs: {
"componentprop": componentProp
}
})], 1)
}
}
)
2.3 vnode
VNode {
asyncFactory: undefined,
asyncMeta: undefined,
children: [VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode, VNode],
componentInstance: undefined,
componentOptions: undefined,
context: Vue {_uid: 0, _isVue: true, __v_skip: true, _scope: EffectScope, $options: {…}, …},
data: {attrs: {…}},
elm: div#app,
fnContext: undefined,
fnOptions: undefined,
fnScopeId: undefined,
isAsyncPlaceholder: false,
isCloned: false,
isComment: false,
isOnce: false,
isRootInsert: true,
isStatic: false,
key: undefined,
ns: undefined,
parent: undefined,
raw: false,
tag: "div",
text: undefined,
child: (...)
}