模板编译的原理
模板编译的原理核心主要分为三部分
1. 将html模板解析成AST对象
解析器有 HTML解析器、文本解析器、过滤解析器
解析
- 通过循环遍历
html模板字符串,依次去处理其中的各个标签、以及去标签上的属性
- 处理开始标签、结束标签、文本节点和注释节点
- 创建AST对象,处理标签上的一些指令、v-bind、v-for、v-if、key、插槽、class、style、属性
HTML解析器的原理
一小段一小段地截取模板字符串,每截取一小段字符串,就会根据截取出来的字符串类型触发不同的钩子函数start、chars(处理文本),直到模板字符串截空停止运行
`<div>
{{name}}
</div>`
第一次循环时,截取出一段字符串<div>,平且触发钩子函数start,截取后的结果为:
`
{{name}}
</div>`
第二次循环时,截取出一段字符串 有空格
`
`
并且触发钩子函数chars(处理文本)截取后的结果为:
`{{name}}
</div>`
依次类推,直到截取出一段字符串</div>,并且触发钩子函数end,截取后的结果为:
``
2. 遍历AST标记生成静态节点
优化
- 将AST树做静态标记;会去遍历AST树,为每个节点做静态标记,标记是否为静态节点,以及进一步去标记静态根节点
静态标记的作用
-
每次重新渲染时,不需要为静态子树创建新节点
-
每次虚拟DOM时打补丁的过程可以跳过
什么节点可以标记为静态节点
- 文本节点
- 节点上没有 v-bind、v-for、v-if 等指令
- 非组件
3. 使用AST生成渲染函数
从 AST 生成渲染函数
将AST对象生成渲染函数,就大家说的render动态渲染函数,负责生成动态节点的 vnode;其实还有一个静态节点的渲染函数,负责生成静态节点的 vnode。
一个简单的模板
<p title="jiandan" @click="c">123</p>
生成后的代码字符串是
with(this){return _c('p',{attrs:{"title":"jiandan",on:{"click":c}},[_v("123")])}
_c 钩子函数:负责生成组件或 html元素的 VNode ; _v 钩子函数:负责文本内容。
再将代码字符串放到函数里,这个函数就叫render 函数
代码生成,将 ast 转换成可执行的 render 函数的字符串形式,
字符串代码通过 new Function(codeStr) 转换成可执行的函数最后生成VNode
const code = generate(ast, options)
code = {
render: `with(this){return ${_c(tag, data, children, normalizationType)}}`, // 动态渲染函数
staticRenderFns: [_c(tag, data, children, normalizationType), ...] // 存放静态渲染函数
}
渲染函数的生成过程
- 渲染函数生成的过程,其实就是在遍历 AST 节点,通过递归的方式,处理每个节点,最后生成形如:
_c(tag, attr, children, normalizationType)的结果
render函数场景
- 编译器编译组件模板生成render选项
- 用户自己提供,用render选项代替模板
渲染过程
- 调用编译
compile函数,生成render函数字符串 - 通过调用
new Watcher()函数,监听数据的变化,当数据发生变化时,然后更新render函数生成vnode - 最后调用
patch()方法,对比新旧节点vnode对象,通过DOM的diff算法,将vnode生成真正的DOM,去更新视图