vue编译过程经过了parse以及optimize的过程后,就是对ast进行codegen过程了 看大佬的文章# Vue模板编译原理 由于这篇文章对codegen过程没有详细阐述,那么。。我就结合一个例子来阐述一下
<ul :class="bindCls" class="list" v-if="isShow">
<li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
最终生成的render函数为
with(this){
return (isShow) ?
_c('ul', {
staticClass: "list",
class: bindCls
},
_l((data), function(item, index) {
return _c('li', {
on: {
"click": function($event) {
clickItem(index)
}
}
},
[_v(_s(item) + ":" + _s(index))])
})
) : _e()
}
ok 正式开始单步过程,ast这里也不关心,其实我自己觉得把ast列出了看到那么多属性也没用,还不如理解为解析过程中进啥分支就知道有啥属性:
generate 入口
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
// genElement核心 进!
const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
genElement 核心
/*处理element,分别处理static静态节点、v-once、v-for、v-if、template、slot以及组件或元素*/
function genElement (el: ASTElement): string {
if (el.staticRoot && !el.staticProcessed) {
/*处理static静态节点*/
return genStatic(el)
} else if (el.once && !el.onceProcessed) {
/*处理v-once*/
return genOnce(el)
} else if (el.for && !el.forProcessed) {
/*处理v-for*/
return genFor(el)
} else if (el.if && !el.ifProcessed) {
/*处理v-if*/
return genIf(el)
} else if (el.tag === 'template' && !el.slotTarget) {
/*处理template*/
return genChildren(el) || 'void 0'
} else if (el.tag === 'slot') {
/*处理slot*/
return genSlot(el)
} else {
// component or element
/*处理组件或元素*/
let code
if (el.component) {
code = genComponent(el.component, el)
} else {
const data = el.plain ? undefined : genData(el)
const children = el.inlineTemplate ? null : genChildren(el, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// module transforms
for (let i = 0; i < transforms.length; i++) {
code = transforms[i](el, code)
}
return code
}
}
这里进入处理v-if的分支genIf
v-if
function genIf (el: any): string {
/*标记位*/
// 这里标记el节点已经处理过了if,防止递归调用genElement
// 时进入死循环
el.ifProcessed = true // avoid recursion
return genIfConditions(el.ifConditions.slice())
}
/*处理if条件*/
function genIfConditions (conditions: ASTIfConditions): string {
/* 表达式不存在 也就是 v-if="" */
if (!conditions.length) {
return '_e()'
}
const condition = conditions.shift()
if (condition.exp) {
// 重点是这的三目表达式
return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}`
} else {
return `${genTernaryExp(condition.block)}`
}
function genTernaryExp (el) {
return el.once ? genOnce(el) : genElement(el)
}
这里核心是三目表达式,但是在生成表达式之前递归调用了genElement,这次调用因为v-if已经打上了标记,不会重复处理v-if,于是就会一路走到 else分支genData中
genData
genData生成一个json字符串
function genData (el: ASTElement): string {
let data = '{'
// directives first.
// directives may mutate the el's other properties before they are generated.
const dirs = genDirectives(el)
if (dirs) data += dirs + ','
// key
if (el.key) {
data += `key:${el.key},`
}
// ref
if (el.ref) {
data += `ref:${el.ref},`
}
if (el.refInFor) {
data += `refInFor:true,`
}
// pre
if (el.pre) {
data += `pre:true,`
}
// record original tag name for components using "is" attribute
if (el.component) {
data += `tag:"${el.tag}",`
}
// module data generation functions
for (let i = 0; i < dataGenFns.length; i++) {
data += dataGenFns[i](el)
}
// attributes
if (el.attrs) {
data += `attrs:{${genProps(el.attrs)}},`
}
// DOM props
if (el.props) {
data += `domProps:{${genProps(el.props)}},`
}
// event handlers
if (el.events) {
data += `${genHandlers(el.events, false, warn)},`
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true, warn)},`
}
// slot target
if (el.slotTarget) {
data += `slot:${el.slotTarget},`
}
// scoped slots
if (el.scopedSlots) {
data += `${genScopedSlots(el.scopedSlots)},`
}
// component v-model
if (el.model) {
data += `model:{value:${
el.model.value
},callback:${
el.model.callback
},expression:${
el.model.expression
}},`
}
// inline-template
if (el.inlineTemplate) {
const inlineTemplate = genInlineTemplate(el)
if (inlineTemplate) {
data += `${inlineTemplate},`
}
}
data = data.replace(/,$/, '') + '}'
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data)
}
return data
}
在这个case中,其他的不会命中,直接进入循环,遍历平台下每个dataGenFuns,拼接到data串中
for (let i = 0; i < dataGenFns.length; i++) {
data += dataGenFns[i](el)
}
对于web平台,只有两个dataGenFuns(weex平台暂且不表):
来进去康康:
class.js中的genData
function genData (el: ASTElement): string {
let data = ''
if (el.staticClass) {
// class = "foo"
data += `staticClass:${el.staticClass},`
}
if (el.classBinding) {
// :class = "barrrrr"
data += `class:${el.classBinding},`
}
return data
}
style.js 中的genData
// 一样的道理
function genData (el: ASTElement): string {
let data = ''
if (el.staticStyle) {
data += `staticStyle:${el.staticStyle},`
}
if (el.styleBinding) {
data += `style:(${el.styleBinding}),`
}
return data
}
回到index.js 的genData:没有其他命中的分支了
这里直接data = data.replace(/,$/, '') + '}'
一个正则匹配到结尾的逗号,然后replace到封口的花括号结束返回data"{staticClass:"list",class:bindCls}"
调用栈回到了genElement,处理好了data,现在处理子节点 genChildren
genChildren
/*处理chidren*/
function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
const children = el.children
if (children.length) {
const el: any = children[0]
// optimize single v-for
/*优化单个v-for*/
if (children.length === 1 &&
el.for &&
el.tag !== 'template' &&
el.tag !== 'slot') {
return genElement(el)
}
const normalizationType = checkSkip ? getNormalizationType(children) : 0
/*用genNode处理children,内部有子节点也会继续遍历*/
return `[${children.map(genNode).join(',')}]${
normalizationType ? `,${normalizationType}` : ''
}`
}
}
/*处理节点*/
function genNode (node: ASTNode): string {
if (node.type === 1) {
return genElement(node)
} else {
return genText(node)
}
}
这里会触发一个v-for的性能优化当只有一个子节点并且是v-for节点的时候,非template或者slot 直接调用genElement,好的,又回到了genElement。在这次调用中,会触发v-for的处理分支genFor
genFor
/*处理v-for循环*/
function genFor (el: any): string {
const exp = el.for // exp: "data"
const alias = el.alias // alias: "item"
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
if (
process.env.NODE_ENV !== 'production' &&
maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key
) {
warn(
`<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
`v-for should have explicit keys. ` +
`See https://vuejs.org/guide/list.html#key for more info.`,
true /* tip */
)
}
/*标记位,避免递归*/
el.forProcessed = true // avoid recursion
// 还是把自己传入genElement 但是因为 v-for已经被标记过
// 因此会继续往下走,这里会走到genData
return `_l((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${genElement(el)}` +
'})'
}
这里因为再次genElement调用了自己,v-for已经处理过,这里会直接走到genData,在genData中,处理了@click 也就是el.events分支调用 genHandlers 过于复杂日后再说,这里只是说返回了data = "{on:{"click":function($event){return clickItem(index)}}}"
到这里 code已经成为了
"_c('li',{on:{"click":function($event){return clickItem(index)}}},[_v(_s(item)+":"+_s(index))])"
return 回去,还记得吗,回到了 genFor return 回到genChildren return 回到了 genElement(处理ul v-for那个)
_c(
'ul',
{ staticClass: 'list', class: bindCls },
_l(data, function (item, index) {
return _c(
'li',
{
on: {
click: function ($event) {
return clickItem(index)
}
}
},
[_v(_s(item) + ':' + _s(index))]
)
}),
0
)
其实就是,_c函数包裹,第一个参数是tag,第二个参数是genData,第三个参数是v-for函数:_l(迭代器,function(你写的alise) return _c(迭代元素,tag,data,children)
回到最外层的genIf 三目运算符套上去,完工!
isShow
? _c(
'ul',
{ staticClass: 'list', class: bindCls },
_l(data, function (item, index) {
return _c(
'li',
{
on: {
click: function ($event) {
return clickItem(index)
}
}
},
[_v(_s(item) + ':' + _s(index))]
)
}),
0
)
: _e()