文章内容输出来源:拉勾教育大前端高薪训练营
在拉钩训练营学习已经有一段时间了,感觉这段时间的收获远比自己独自学习强的多,自己学习的时候经常会因为惰性,无法坚持,在这里有班主任时刻关注你的学习进度(感觉对我这种懒人蛮好的),最重要的是有了一个学习的计划,不用无头苍蝇一样东一点西一点,最后什么都没记住。学习都是需要方法和技巧的,在训练营会有专业的老师为你答疑解惑,同时会教你解决问题的思路及方法,让你能够触类旁通。这篇文章主要是Vue源码学习中的模板编译相关:
//带编译器版本的 Vue.js 中,使用 template 或 el 的方式设置模板
<script>
Vue.component('comp', {
template: 'I am a comp'
})
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello compiler'
},
methods: {
handler () {
console.log('test')
}
//编译后 render 输出的结果
(function anonymous() {
with (this) {
return _c(
"div",
{ attrs: { id: "app" } },
[
_m(0),
_v(" "),
_c("p", [_v(_s(msg))]),
_v(" "),
_c("comp", { on: { myclick: handler } }),
],
1
);
}
});
</script>
Vue.prototype.$mount = function (
……
// 把 template 转换成 render 函数src\platforms\web\entry-runtime-with-compiler.js
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
……
)
//src\platforms\web\compiler\index.js
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
//src\compiler\index.js
import { createCompilerCreator } from './create-compiler'
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
...省略
})
//src\platforms\web\compiler\create-compiler.js
import { createCompileToFunctionFn } from './to-function'
export function createCompilerCreator (baseCompile: Function): Function {
return function createCompiler (baseOptions: CompilerOptions) {
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
...省略
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
compileToFunctions就是我们模板编译的入口文件,而这个文件实际是从
src\platforms\web\compiler\to-function.js中引入的:
export function createCompileToFunctionFn (compile: Function): Function {
const cache = Object.create(null)
return function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
options = extend({}, options)
const warn = options.warn || baseWarn
delete options.warn
// check cache
//1.0读取缓存中的CompiledFunctionResult对象,如果有直接返回
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// compile
//2.0将模板编译为编译对象{render,staticRenderFns},字符串形式的js代码
const compiled = compile(template, options)
// turn code into functions
const res = {}
const fnGenErrors = []
//3.0将字符串形式的js代码转换为js方法
res.render = createFunction(compiled.render, fnGenErrors)
res.staticRenderFns = compiled.staticRenderFns.map(code => {
return createFunction(code, fnGenErrors)
})
//4.0缓存并返回res对象{render,staticRenderFns}
return (cache[key] = res)
}
}
我们可以看到最终编译我们使用了compiler函数,我们看下compiler中做了什么:
function compile (
template: string,//模板
options?: CompilerOptions//
): CompiledResult {
const finalOptions = Object.create(baseOptions)//合并baseOptions 与CompilerOptions
const errors = []
const tips = []
let warn = (msg, range, tip) => {
(tip ? tips : errors).push(msg)
}
if (options) {
// merge custom modules
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// merge custom directives
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
)
}
// copy other options
for (const key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key]
}
}
}
finalOptions.warn = warn
const compiled = baseCompile(template.trim(), finalOptions)
compiled.errors = errors
compiled.tips = tips
return compiled
}
所以在compile中主要是用于合并选项,调用baseCompile进行编译记录错误,返回编译好的对象,
baseCompile是编译的核心函数,主要做了三件事情:
function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
//将模板转换为ast抽象语法树,以树形结构描述代码结构
//什么是抽象语法树?
//抽象语法树简称AST(Abstract Syntax Tree)
//使用对象形式描述属性的代码结构
//此处的抽象语法树是用来描述树形结构的HTML字符串
//为社么使用抽象语法树?
//模板字符串转换为AST后,可以使用AST对模板进行优化处理
//标记模板的静态内容,在patch的时候跳过静态内容
//patch过程中静态内容不需要对比和重新渲染
const ast = parse(template.trim(), options)
//优化语法树
if (options.optimize !== false) {
optimize(ast, options)
}
//将语法树生成字符串形式的js代码
const code = generate(ast, options)
return {
ast,
//渲染函数--字符串形式需要new Function转化
render: code.render,
//静态渲染函数 生成静态vNode树
staticRenderFns: code.staticRenderFns
}
}
我们查看下Vue生成的AST的过程(parse的执行流程借鉴了simpleHtmlparse.js):
我们将模板转换为ast后,在使用optimize对其进行优化:
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
// 标记静态节点
markStatic(root)
// 标记静态根节点-是静态节点具有children且不为文本节点
markStaticRoots(root, false)
}
当优化完成后我们需要将其转化为字符串形式的js代码。