学习目标
- 模板编译整体过程
- 组件化机制
源码环境
"name": "vue",
"version": "2.6.11",
源码的目录结构,可以看下 vue源码解析(一)——初始化流程及数据响应式过程梳理
我将源码分析分为三个模块进行了分析,可以互相结合看下,学习起来更轻松下。
模板编译整体过程
解析
编辑器可以将模板解析成AST抽象语法树;通过遍历这颗对象树生成代码
路径:src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 1.将html字符串解析成AST(语法抽象树)
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
parse() 路径:src/compiler/parser/index.js
export function parse (
template: string,
options: CompilerOptions
): ASTElement | void {
// 开始编译 解析相关变量
const stack = []
// 解析html的相关代码
parseHTML(template, {
// 遇到起始标签调用
start (tag, attrs, unary, start, end) {
.......
// 遇到一个起始节点就创建一个ASTElement元素,单根的一个对象
let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}
.......
if (inVPre) {
processRawAttrs(element)
} else if (!element.processed) {
// for if noce 需要提前处理
processFor(element)
processIf(element)
processOnce(element)
}
......
},
})
parseHTML路径:src/compiler/parser/index.js
因为要匹配HTML,所以里面都是正则表达式,各种匹配
优化
优化器的作用是在AST中找出静态子树并打上标记。静态子树是在AST中永远不变的节点,如纯文本节点。
路径:src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
2.优化的核心目标,标记静态节点
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
什么是静态节点
两级包裹,纯文本,比如:静态节点
//静态根节点里面是静态节点
递归找到静态根节点,如果children 也是静态节点,那就定义为静态节点。未来就会生成静态函数,在做patch的之后会跳过vue的处理工作方式:官方认为,如果只有一标签,消耗有些大,去占用内存。所以静态节点指的两层都为静态节点
代码生成
将AST转换成渲染函数中的内容,即代码字符串。
路径:src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
3.将ast生成为函数字符串,还不是函数,而是函数的字符串
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
generate 路径:src/compiler/codegen/index.js
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
// 从这里可以看出 for的优先级比if高
// 一个标签从if 和 for 首先执行for 肚子里包着if
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else { }
}
genFor 路径:src/compiler/codegen/index.js
export function genFor (
el: any,
state: CodegenState,
altGen?: Function,
altHelper?: string
): string {
// v-for="item in items"
// exp===items
// el.alias === item
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' // index
const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' //key??
.........
el.forProcessed = true // avoid recursion
// 最后生成的代码是一串子字符串
// altHelper 通常情况不存在
// _l(exp,functin(alias,iterator1,iterator2){return })
return `${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${(altGen || genElement)(el, state)}` +
'})'
}
组件化机制
组件声明:Vue.component()
去寻找为什么Vue.component()声明的组建可以全局使用
例如:
https://www.processon.com/vie...源码分为三块进行了整理:
可以在主页一一查看。