跳到主要内容

认识

2023年07月17日
柏拉文
越努力,越幸运

一、认识


Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。在 Vue.js 中,Virtual DOM 是用 VNode 这么一个 Class 去描述。

VNode生成过程如下:

  1. 组件实例初始化,最后执行 $mount 进入挂载阶段

    • 如果是只包含运行时的 vue.js,只直接进入挂载阶段,因为这时候的组件已经变成了渲染函数,编译过程通过模块打包器 + vue-loader + vue-template-compiler 完成的

    • 如果没有使用预编译,则必须使用全量的 vue.js

  2. 挂载时如果发现组件配置项上没有 render 选项,则进入编译阶段

  3. 将模版字符串编译成 AST 语法树,其实就是一个普通的 JS 对象

  4. 然后优化 AST,遍历 AST 对象,标记每一个节点是否为静态静态;然后再进一步标记出静态根节点,在组件后续更新时会跳过这些静态节点的更新,以提高性能

  5. 接下来从 AST 生成渲染函数,生成的渲染函数有两部分组成:

    • 负责生成动态节点 VNoderender 函数

    • 还有一个 staticRenderFns 数组,里面每一个元素都是一个生成静态节点 VNode 的函数,这些函数会作为 render 函数的组成部分,负责生成静态节点的 VNode

  6. 接下来将渲染函数放到组件的配置对象上,进入挂载阶段,即执行 mountComponent 方法

  7. 最终负责渲染组件和更新组件的是一个叫 updateComponent 方法,该方法每次执行前首先需要执行 vm._render 函数,该函数负责执行编译器生成的 render,得到组件的 VNode

  8. 将一个组件生成 VNode 的具体工作是由 render 函数中的 _c_o_l_m 等方法完成的,这些方法都被挂载到 Vue 实例上面,负责在运行时设置组件配置信息,然后通过 new VNode(组件信息) 生成组件的 VNode

  9. _c 负责生成组件或 HTML 元素的 VNode, _c 是所有 render helper 方法中最复杂,也是最核心的一个方法,其它的 _xx 都是它的组成部分

    1. 接收标签、属性 JSON 字符串、子节点数组、节点规范化类型作为参数

    2. 如果标签是平台保留标签或者一个未知的元素,则直接 new VNode(标签信息) 得到 VNode

    3. 如果标签是一个组件,则执行 createComponent 方法生成 VNode

      1. 函数式组件执行自己的 render 函数生成 VNode

      2. 普通组件则实例化一个 VNode,并且在在 data.hook 对象上设置 4 个方法,在组件的 patch 阶段会被调用,从而进入子组件的实例化、挂载阶段,然后进行编译生成渲染函数,直至完成渲染

      3. 当然生成 VNode 之前会进行一些配置处理比如:

        1. 子组件选项合并,合并全局配置项到组件配置项上

        2. 处理自定义组件的 v-model

        3. 处理组件的 props,提取组件的 props 数据,以组件的 props 配置中的属性为 key,父组件中对应的数据为 value 生成一个 propsData 对象;当组件更新时生成新的 VNode,又会进行这一步,这就是 props 响应式的原理

        4. 处理其它数据,比如监听器

        5. 安装内置的 initprepatchinsertdestroy 钩子到 data.hooks 对象上,组件 patch 阶段会用到这些钩子方法

  10. _l 运行时渲染 v-for 列表的帮助函数,循环遍历 val 值,依次为每一项执行 render 方法生成 VNode,最终返回一个 VNode 数组

  11. _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: (...)
}