之前有提到,vue中的templete最后都会编译成render函数来执行,编译的过程主要由parse,optimize,generate组成,下面我们来聊聊optimize和generate
optimize,翻译成中文是优化,而optimize完成的正是优化的过程,optimize通过对节点添加标志,从而在页面重新刷新进行vnode比对时,跳过静态节点,提高性能,下面我们进入源码
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
// first pass: mark all non-static nodes.
markStatic(root)
// second pass: mark static roots.
markStaticRoots(root, false)
}
主要执行两个方法,markStatic
和markStaticRoots
,root即为通过parse阶段生成的ast抽象语法树
我们进入markStatic
function markStatic (node: ASTNode) {
node.static = isStatic(node)
if (node.type === 1) {
// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
if (!child.static) {
node.static = false
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if (!block.static) {
node.static = false
}
}
}
}
}
首先调用isStatic
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { // expression
return false
}
if (node.type === 3) { // text
return true
}
return !!(node.pre || (
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
可以看到,如果node 是表达式,返回false,如果是文本节点,返回true,如果有v-for click等的属性也返回false,
然后回到markStatic,可以看到会对node的children子节点递归调用markStaic,如果子节点不是static,就会把当前节点也置为非static,
这就是markStatic的主要过程,
下面我们进入markstaticRoot
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor
}
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
可以看到,如果一个节点是static的,且他有子节点,且不是只有一个children,那就会把节点标记staticRoot,并会对子节点进行递归调用,
从而对所有节点进行标记,这就是optimize的主要内容,
编译的最后一个步骤是generate,这里是真正的生成render函数,最后返回的是一个字符串,可由new Function或eval进行执行
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
主要执行了genElement方法,我们进入genElement方法
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
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 {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el, state)
} else {
let data
if (!el.plain || (el.pre && state.maybeComponent(el))) {
data = genData(el, state)
}
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
可以看到,这时会根据不同的节点属性执行不同的方法,例如如果只是普通的节点会或返回一个code字符串,内容为'_c(xx'
,_c这些方法是什么呢,其实这些方法都定义在辅助函数中
export function installRenderHelpers (target: any) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
}
因为情况非常多,我们就拿v-for来举例说明一下执行过程,实例templete为
, genFor源码
export function genFor (
el: any,
state: CodegenState,
altGen?: Function,
altHelper?: string
): string {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
if (process.env.NODE_ENV !== 'production' &&
state.maybeComponent(el) &&
el.tag !== 'slot' &&
el.tag !== 'template' &&
!el.key
) {
state.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.`,
el.rawAttrsMap['v-for'],
true /* tip */
)
}
el.forProcessed = true // avoid recursion
return `${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${(altGen || genElement)(el, state)}` +
'})'
}
可以看到首先对templete类型的key进行了判断,如果没有,会进行警告,最后返回了一个_l
作为函数名的执行函数,参数为定义的v-for
值,函数内容为另一个genElement
,最后整个templete返回的render函数内容为
"with(this){return _c('ul',_l(([1,2,3]),function(item){return _c('li',[_v(_s(item))])}),0)}"
最后render函数在执行时就会把_c和_l等替换成真正的函数,从而返回一个vnode,再继续完成patch,插入真实dom树完成渲染,
到现在为止,templete 编译过程已经完成。