Vue初始化
还是从Vue
初始化开始,调试模板是如何进行编译的
<template>
<div id="app">
<h1><span>hello</span>Vue</h1>
<div>{{ message }}</div>
</div>
</template>
<script>
const vm = new Vue({
el: '#app',
data:{
message: "hello world",
},
});
</script>
在之前的首次渲染分析中,我们知道vue是通过src/platforms/web/entry-runtime-with-compiler.js
中compileToFunctions
这个函数,把模板转成render函数的,我们先在compileToFunctions
的时候打个断点。接下来进行调试
模板编译流程图
下面的流程就是compileToFunctions
的执行过程,可以参照这个流程图进行下面的调试过程
调试
进入了compileToFunctions
函数发现他在src/compiler/to-function.js
中定义
export function createCompileToFunctionFn (compile: Function): Function {
/** 1. 使用模板字符串作为key,来缓存中查找是否有编译好的渲染函数,如果有的话直接返回
* 首次执行还没有缓存渲染函数,这里会继续执行
*/
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
/** 2. 调用compile函数开始编译模板
* 调用compile把 template 编译为对象(render, staticRenderFns), 字符串形式的js代码
* 我们进入compile内部如何进行编译的
*/
const compiled = compile(template, options)
/** 3. 把字符串形式的js代码转成js方法 */
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}
/** 4. 缓存并返回res对象(render, staticRenderFns方法) */
return (cache[key] = res)
}
compile
这个函数在src/compiler/create-compiler.js
中定义,compile
的核心作用就是合并options
,调用baseCompile
进行编译,最后记录错误返回编译好的对象
export function createCompilerCreator (baseCompile: Function): Function {
return function createCompiler (baseOptions: CompilerOptions) {
// baseOptions 平台相关的options ,sec/platform/web/compiler/options.js中定义
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
/** 1. 合并baseOptions 和 compile函数的options */
const finalOptions = Object.create(baseOptions)
...
/** 2. baseCompile 返回的字符串形式的render代码 和 staticRenderFns
* baseCompile 是编译的核心函数,进入baseCompile看下是如何实现的
*/
const compiled = baseCompile(template.trim(), finalOptions)
}
}
}
baseCompile
这个函数在src/compiler/index.js
中定义,主要做了三件事
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
/** 1. 把模板编译成ast 抽象语法树
* 抽象语法树,用来以树形方式描述代码结构
*/
const ast = parse(template.trim(), options)
/** 2. 优化语法树 */
if (options.optimize !== false) {
optimize(ast, options)
}
/** 3. 把抽象语法树生成字符串形式的 js 代码 */
const code = generate(ast, options)
return {
ast,
// 渲染函数,注意:此时render是字符串形式的
render: code.render,
// 静态渲染函数,生成静态 VNode 树
staticRenderFns: code.staticRenderFns
}
})
截止到现在主线中字符串形式的代码都已经生成了,下面回到主线该去执行第三步,把字符串形式的代码转成js方法
🦍这里面做个关于抽象语法树的知识点补充
什么是抽象语法树
- 抽象语法树简称AST
- 使用对象的形式描述树形的代码结构
- 此处的抽象语法树是用来形容树形结构的HTML字符串
为什么使用抽象语法树
- 模板字符串转换成AST后,可以通过AST对模板进行优化
- 标记模板中的静态内容,在patch的时候直接跳过静态内容
- 在patch的过程中静态内容不需要对比和重新渲染
总结
模板编译就是把模板字符串转成渲染函数。
- 模板编译首先把模板转成
ast
对象, 然后优化ast对象。优化的过程实际就是在标记静态根节点, - 把优化好的
ast
对象转成字符串形式代码。 - 最终在字符串形式的代码通过
new Function
转成匿名函数,这个匿名函数就是最终生成的render
函数。