編譯的最後一步就是把優化後的 AST
樹轉換成可執行的代碼,這部份內容也比較多,瞭解總體流程便可。部分細節咱們會在以後的章節配合一個具體 case
去詳細講。html
看這個例子:vue
<ul :class="bindCls" class="list" v-if="isShow">
<li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
複製代碼
它通過編譯,執行 const code = generate(ast, options)
,生成的 render
代碼串以下:node
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()
}
複製代碼
這裏的 _c
函數定義在 src/core/instance/render.js
中。web
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
複製代碼
而 _e
、_l
、_v
定義在 src/core/instance/render-helpers/index.js
中:express
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
}
複製代碼
顧名思義,_c
就是執行 createElement
去建立 VNode
,而 _l
對應 renderList
渲染列表;_v
對應 createTextVNode
建立文本 VNode
;_e
對於 createEmptyVNode
建立空的 VNode
。bash
在 compileToFunctions
中,會把這個 render
代碼串轉換成函數,它的定義在 src/compler/to-function.js
中:app
const compiled = compile(template, options)
res.render = createFunction(compiled.render, fnGenErrors)
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}
複製代碼
實際上就是把 render
代碼串經過 new Function
的方式轉換成可執行的函數,賦值給 vm.options.render
,這樣當組件經過 vm._render
的時候,就會執行這個 render
函數。那麼接下來咱們就重點關注一下這個 render
代碼串的生成過程。dom
const code = generate(ast, options)
複製代碼
generate
函數的定義在 src/compiler/codegen/index.js
中:編輯器
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
}
}
複製代碼
generate
函數首先經過 genElement(ast, state)
生成 code
,再把 code
用 with(this){return ${code}}}
包裹起來。這裏的 state
是 CodegenState
的一個實例,稍後咱們在用到它的時候會介紹它。先來看一下 genElement
:ide
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
}
}
複製代碼
基本就是判斷當前 AST
元素節點的屬性執行不一樣的代碼生成函數,在咱們的例子中,咱們先了解一下 genFor
和 genIf
。
export function genIf (
el: any,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
el.ifProcessed = true // avoid recursion
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (
conditions: ASTIfConditions,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
if (!conditions.length) {
return altEmpty || '_e()'
}
const condition = conditions.shift()
if (condition.exp) {
return `(${condition.exp})?${
genTernaryExp(condition.block)
}:${
genIfConditions(conditions, state, altGen, altEmpty)
}`
} else {
return `${genTernaryExp(condition.block)}`
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
複製代碼
genIf
主要是經過執行 genIfConditions
,它是依次從 conditions
獲取第一個 condition
,而後經過對 condition.exp
去生成一段三元運算符的代碼。
?
後調用genTernaryExp
,這裏不考慮 v-once
的狀況,最終是調用了 genElement
。
:
後遞歸調用 genIfConditions
,這樣若是有多個 conditions
,就生成多層三元運算邏輯。
在咱們的例子中,只有一個 condition.exp
爲 isShow
,所以生成以下僞代碼:
return (isShow) ? genElement(el, state) : _e()
複製代碼
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)}` +
'})'
}
複製代碼
genFor
的邏輯很簡單,首先 AST
元素節點中獲取了和 for
相關的一些屬性,而後返回了一個代碼字符串。
在咱們的例子中,exp
是 data
,alias
是 item
,iterator1
是 index
,所以生成以下僞代碼:
_l((data), function(item, index) {
return genElememt(el, state)
})
複製代碼
再次回顧咱們的例子,它的最外層是 ul
,首先執行 genIf
,它最終調用了 genElement(el, state)
去生成子節點,注意,這裏的 el
仍然指向的是 ul
對應的 AST
節點,可是此時的 el.ifProcessed
爲 true
,因此命中最後一個 else
邏輯:
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
}
複製代碼
這裏咱們只關注 2 個邏輯,genData
和 genChildren
:
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
// directives first.
// directives may mutate the el's other properties before they are generated.
const dirs = genDirectives(el, state)
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 < state.dataGenFns.length; i++) {
data += state.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)},`
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true)},`
}
// slot target
// only for non-scoped slots
if (el.slotTarget && !el.slotScope) {
data += `slot:${el.slotTarget},`
}
// scoped slots
if (el.scopedSlots) {
data += `${genScopedSlots(el, el.scopedSlots, state)},`
}
// 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, state)
if (inlineTemplate) {
data += `${inlineTemplate},`
}
}
data = data.replace(/,$/, '') + '}'
// v-bind dynamic argument wrap
// v-bind with dynamic arguments must be applied using the same v-bind object
// merge helper so that class/style/mustUseProp attrs are handled correctly.
if (el.dynamicAttrs) {
data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`
}
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data)
}
// v-on data wrap
if (el.wrapListeners) {
data = el.wrapListeners(data)
}
return data
}
複製代碼
genData
函數就是根據 AST
元素節點的屬性構造出一個 data
對象字符串,這個在後面建立 VNode
的時候的時候會做爲參數傳入。
以前咱們提到了 CodegenState
的實例 state
,這裏有一段關於 state
的邏輯:
export function genData (el: ASTElement, state: CodegenState): string {
// ...
// module data generation functions
for (let i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el)
}
// ...
}
複製代碼
state.dataGenFns
的初始化在它的構造器中。
export class CodegenState {
constructor (options: CompilerOptions) {
// ...
this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
// ...
}
}
複製代碼
實際上就是獲取全部 modules
中的 genData
函數,其中,class module
和 style module
定義了 genData
函數。好比定義在 src/platforms/web/compiler/modules/class.js
中的 genData
方法:
function genData (el: ASTElement): string {
let data = ''
if (el.staticClass) {
data += `staticClass:${el.staticClass},`
}
if (el.classBinding) {
data += `class:${el.classBinding},`
}
return data
}
複製代碼
在咱們的例子中,ul AST
元素節點定義了 el.staticClass
和 el.classBinding
,所以最終生成的 data 字符串以下:
{
staticClass: "list",
class: bindCls
}
複製代碼
接下來咱們再來看一下 genChildren
,它的定義在 src/compiler/codegen/index.js
中:
export function genChildren (
el: ASTElement,
state: CodegenState,
checkSkip?: boolean,
altGenElement?: Function,
altGenNode?: Function
): string | void {
const children = el.children
if (children.length) {
const el: any = children[0]
// optimize single v-for
if (children.length === 1 &&
el.for &&
el.tag !== 'template' &&
el.tag !== 'slot'
) {
const normalizationType = checkSkip
? state.maybeComponent(el) ? `,1` : `,0`
: ``
return `${(altGenElement || genElement)(el, state)}${normalizationType}`
}
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
const gen = altGenNode || genNode
return `[${children.map(c => gen(c, state)).join(',')}]${
normalizationType ? `,${normalizationType}` : ''
}`
}
}
複製代碼
在咱們的例子中,li AST
元素節點是 ul AST
元素節點的 children
之一,知足 (children.length === 1 && el.for && el.tag !== 'template' && el.tag !== 'slot')
條件,所以經過 genElement(el, state)
生成 li AST
元素節點的代碼,也就回到了咱們以前調用 genFor
生成的代碼,把它們拼在一塊兒生成的僞代碼以下:
return (isShow) ?
_c('ul', {
staticClass: "list",
class: bindCls
},
_l((data), function(item, index) {
return genElememt(el, state)
})
) : _e()
複製代碼
在咱們的例子中,在執行 genElememt(el, state)
的時候,el
仍是 li AST
元素節點,el.forProcessed
已爲 true
,因此會繼續執行 genData
和 genChildren
的邏輯。因爲 el.events
不爲空,在執行 genData
的時候,會執行 以下邏輯:
if (el.events) {
data += `${genHandlers(el.events, false, state.warn)},`
}
複製代碼
genHandlers
的定義在 src/compiler/codegen/events.js
中:
export function genHandlers (
events: ASTElementHandlers,
isNative: boolean,
warn: Function
): string {
let res = isNative ? 'nativeOn:{' : 'on:{'
for (const name in events) {
res += `"${name}":${genHandler(name, events[name])},`
}
return res.slice(0, -1) + '}'
}
複製代碼
genHandler
的邏輯就不介紹了,很大部分都是對修飾符 modifier
的處理,感興趣同窗能夠本身看,對於咱們的例子,它最終 genData
生成的 data
字符串以下:
{
on: {
"click": function($event) {
clickItem(index)
}
}
}
複製代碼
genChildren
的時候,會執行到以下邏輯:
export function genChildren (
el: ASTElement,
state: CodegenState,
checkSkip?: boolean,
altGenElement?: Function,
altGenNode?: Function
): string | void {
// ...
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
const gen = altGenNode || genNode
return `[${children.map(c => gen(c, state)).join(',')}]${
normalizationType ? `,${normalizationType}` : ''
}`
}
function genNode (node: ASTNode, state: CodegenState): string {
if (node.type === 1) {
return genElement(node, state)
} if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
}
}
複製代碼
genChildren
的就是遍歷 children
,而後執行 genNode
方法,根據不一樣的 type
執行具體的方法。在咱們的例子中,li AST
元素節點的 children
是 type
爲 2
的表達式 AST
元素節點,那麼會執行到 genText(node)
邏輯。
export function genText (text: ASTText | ASTExpression): string {
return `_v(${text.type === 2
? text.expression
: transformSpecialNewlines(JSON.stringify(text.text))
})`
}
複製代碼
所以在咱們的例子中,genChildren
生成的代碼串以下:
[_v(_s(item) + ":" + _s(index))]
複製代碼
和以前拼在一塊兒,最終生成的 code
以下:
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()
複製代碼
這一節經過例子配合解析,咱們對從 ast -> code
這一步有了一些瞭解,編譯後生成的代碼就是在運行時執行的代碼。因爲 genCode
的內容有不少,因此我對你們的建議是不必把全部的細節都一次性看完,咱們應該根據具體一個 case
,走完一條主線便可。
在以後的章節咱們會對 slot
的實現作解析,咱們會從新複習編譯的章節,針對具體問題作具體分析,有利於咱們排除干擾,對編譯過程的學習有更深刻的理解。