渲染函数是创建HTML最原始的方法, 模板(可以理解为字符串)最终会通过编译转换成渲染函数, 执行后得到一份vnode用于渲染DOM渲染,其实模板编译是配合虚拟DOM进行渲染的。
关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,这三个部分是有前后关系的:
-
第一步是将
模板字符串
转换成element ASTs
(解析器) -
第二步是对
AST
进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器) -
第三步是 使用
element ASTs
生成render
函数代码字符串(代码生成器)
解析器: 将模板字符串解析成AST
<div>
<p>{{age}}</p>
</div>
解析成如下的AST结构
{
tag: "div"
type: 1,
staticRoot: false,
static: false,
plain: true,
parent: undefined,
attrsList: [],
attrsMap: {},
children: [
{
tag: "p"
type: 1,
staticRoot: false,
static: false,
plain: true,
parent: {tag: "div", ...},
attrsList: [],
attrsMap: {},
children: [{
type: 2,
text: "{{age}}",
static: false,
expression: "_s(age)"
}]
}
]
}
该过程是通过正则匹配进行截取解析
正则解析过程如下:
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
let html = `<div></div>`
let index = 0
const start = html.match(startTagOpen)
const match = {
tagName: start[1],
attrs: [],
start: 0
}
html = html.substring(start[0].length)
index += start[0].length
let end, attr
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
html = html.substring(attr[0].length)
index += attr[0].length
match.attrs.push(attr)
}
if (end) {
match.unarySlash = end[1]
html = html.substring(end[0].length)
index += end[0].length
match.end = index
}
console.log(match)
优化器: 遍历AST,检出所有静态子树
静态节点无论怎样变都不需要重新渲染,当AST中的静态子树被打上静态标签后,每次重新渲染时,不需要新建虚拟节点,直接克隆已存在的虚拟节点即可
代码生成器:将AST转换成渲染函数中的内容,这个内容可以称为“代码字符串”
最后生成的render函数:
{
render: `with(this){return _c('div',[_c('p',[_v(_s(name))])])}`
}
with(this){
return _c(
'div',
[
_c(
'p',
[
_v(_s(name))
]
)
]
)
}
其中_c是createElement
_v是创建文本节点
_s是返回参数中的字符串