认识
一、认识
Vue.js 2.0 Compile
编译器 将 Template
模版编译解析为 render
函数。首先对模版进行词法分析和语法分析, 得到 AST
模版。接着, 将模版 AST
进行优化, 标记静态节点, 最后JavaScript
代码,即渲染函数代码。工作流如下:
二、细节
2.1 解析 parse
-
遍历
HTML
模版字符串,通过正则表达式匹配<
-
跳过某些不需要处理的标签,比如: 注释标签、条件注释标签、
Doctype
。 -
解析开始标签:
-
得到一个对象,包括 标签名(
tagName
)、所有的属性(attrs
)、标签在html
模版字符串中的索引位置 -
进一步处理上一步得到的
attrs
属性,将其变成[{ name: attrName, value: attrVal, start: xx, end: xx }, ...]
的形式 -
通过标签名、属性对象和当前元素的父元素生成
AST
对象,其实就是一个 普通的JS
对象,通过key、value
的形式记录了该元素的一些信息 -
接下来进一步处理开始标签上的一些指令,比如
v-pre
、v-for
、v-if
、v-once
,并将处理结果放到AST
对象上 -
处理结束将
ast
对象存放到stack
数组 -
处理完成后会截断
html
字符串,将已经处理掉的字符串截掉
-
-
解析闭合标签
-
如果匹配到结束标签,就从
stack
数组中拿出最后一个元素,它和当前匹配到的结束标签是一对。 -
再次处理开始标签上的属性,这些属性和前面处理的不一样,比如:
key
、ref
、scopedSlot
、样式等,并将处理结果放到元素的AST
对象上 -
然后将当前元素和父元素产生联系,给当前元素的
ast
对象设置parent
属性,然后将自己放到父元素的ast
对象的children
数组中
-
-
最后遍历完整个
html
模版字符串以后,返回ast
对象
2.2 优化 optimize
optimize
的过程其实就是静态标记的过程。
静态标记的过程如下:
-
标记静态节点
-
通过递归的方式标记所有的元素节点
-
如果节点本身是静态节点,但是存在非静态的子节点,则将节点修改为非静态节点
-
-
标记静态根节点,基于静态节点,进一步标记静态根节点
-
如果节点本身是静态节点 && 而且有子节点 && 子节点不全是文本节点,则标记为静态根节点
-
如果节点本身不是静态根节点,则递归的遍历所有子节点,在子节点中标记静态根
-
标记为静态节点的条件:
-
非组件
-
文本节点
-
节点上没有
v-bind
、v-for
、v-if
等指令
2.3 生成 generate
渲染函数生成过程: 实编译器生成的渲染有两类
-
render
: 负责生成动态节点的vnode
-
staticRenderFns
: 数组中的静态渲染函数,这些函数负责生成静态节点的vnode
渲染函数生成的过程,其实就是在遍历 AST
节点,通过递归的方式,处理每个节点,最后生成形如:_c(tag, attr, children, normalizationType)
的结果。tag
是标签名,attr
是属性对象,children
是子节点组成的数组,其中每个元素的格式都是 _c(tag, attr, children, normalizationTYpe)
的形式,normalization
表示节点的规范化类型,是一个数字 0
、1
、2
。
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>
生成的 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)
}
}
)
三、问题
3.1 .vue 中的 HTML 是真实的 HTML 吗?
在 .vue
文件中的 template
中写入的 html
不是真实的 html
。 原因非常简单, 如果我们写入的是真实的 HTML
节点, 对于 v-if
、v-bind
、keep-alive
这些东西,浏览器显然是不认识的,所以这些东西理应无法解析。但是这些指令或者组件被正确解析了,所以 Vue
一定在中间做了什么,让假的 HTML
节点被渲染成了真实的 HTML
标签节点。