Vue編譯器實現(xiàn)代碼生成方法介紹
這里將討論如何根據(jù)JavaScript AST生成渲染函數(shù)的代碼,即代碼生成。代碼生成本質(zhì)上是字符串拼接的藝術(shù)。需要訪問JavaScript AST中的節(jié)點,為每一種類型的節(jié)點生成相符的 JavaScript代碼。
本節(jié),將實現(xiàn) generate函數(shù)來完成代碼生成的任務。
function compile(template) {
//模板 AST
const ast = parse(template)
//將模板 AST轉(zhuǎn)換為JavaScript AST
transform(ast)
// 代碼生成
const code = generate(ast.jsNode)
return code
}
在這里,代碼生成也需要上下文對象。
function generate(node){
const context = {
// 存儲最終生成的渲染函數(shù)代碼
code: '',
// 在生成代碼時,通過調(diào)用 push 函數(shù)完成代碼的拼接
push(code){
context.code += code
}
}
// 該方法完成代碼生成的工作
genNode(node,context)
// 返回渲染函數(shù)代碼
return context.code
}
另外,為了讓最終生成的代碼具有較強的可讀性,要考慮生成代碼的格式,這就需要擴展context對象,為其增加用來完成換行和縮進的工具函數(shù),如下面代碼所示:
function generate(node){
const context = {
code: '',
push(code){
context.code += code
},
// 當前縮進的級別,初始值為 0,即沒有縮進
currentIndent: 0,
// 該函數(shù)用來換行,即在代碼字符串的后面追加 \n 字符
// 另外,換行時應該保留縮進,所以還要追加 currentIndent * 2 個空格字符
newline(){
context.code += '\n'+` `.repeat(context.currentIndent)
},
// 用來縮進,即讓 currentIndent 自增后,調(diào)用換行函數(shù)
indent(){
context.currentIndent++
context.newline()
},
// 取消縮進
deIndent(){
context.currentIndent--
context.newline()
},
}
genNode(node, context)
return context.code
}
有了這些基礎能力之后,就可以開始編寫 genNode 函數(shù)來完成代碼生成的工作了。代碼生成的原理其實很簡單,只需要匹配各種類型的 JavaScript AST節(jié)點,并調(diào)用對應的生成函數(shù)即可,如下面的代碼所示:
function genNode(node,context){
switch(node.type){
case 'FunctionDecl':
genFunctionDecl(node,context)
break
case 'ReturnStatement':
genReturnStatement(node,context)
break
case 'CallExpression':
genCallExpression(node,context)
break
case 'StringLiteral':
genStringLiteral(node,context)
break
case 'ArrayExpression':
genArrayExpression(node,context)
break
}
}
如果后續(xù)需要增加節(jié)點類型,只需要再genNode函數(shù)中添加相應的處理分支即可
接下來逐步完善代碼生成工作,首先實現(xiàn)函數(shù)生命語句的代碼生成,即genFunctionDecl函數(shù),如下面的代碼所示:
function genFunctionDecl(node,context){
// 從context 對象中取出工具函數(shù)
const {push, indent, deIndent} = context
push(`function ${node.id.name}`)
push(`(`)
//調(diào)用 genNodeList 為函數(shù)的參數(shù)生成代碼
genNodeList(node.params, context)
push(`)`)
push(`{`)
indent()
//為函數(shù)體生成代碼,這里遞歸地調(diào)用了 genNode 函數(shù)
node.body.forEach(n=>genNode(n,context))
deIndent()
push(`}`)
}
genFunctionDecl函數(shù)用來為函數(shù)聲明類型的節(jié)點生成對應的 JavaScript代碼。以聲明節(jié)點為例,它最終生成的代碼將會是:
function render(){
...函數(shù)體
}
genNodeList函數(shù)的實現(xiàn)如下:
function genNodeList(nodes, context){
const {push} = context
for(let i=0;i<nodes.length;i++){
const node = nodes[i]
genNode(node,context)
if(i<nodes.length - 1){
push(',')
}
}
}
這里要注意的一點是,每處理完一個節(jié)點,需要在生成的代碼后面拼接逗號。
實際上,genArrayExpression函數(shù)就利用了這個特點來實現(xiàn)對數(shù)組表達式的代碼生成,如下面的代碼所示:
function genArrayExpression(node, context){
const {push} = context
// 追加方括號
push(`[`)
genNodeList(node.elements, context)
// 補全方括號
push(`]`)
}
對于 genFunctionDecl 函數(shù),另外需要注意的是,由于函數(shù)體本身也是一個節(jié)點數(shù)組,所以需要遍歷它并遞歸地調(diào)用 genNode 函數(shù)生成代碼。
對于ReturnStatement和StringLiteral類型的節(jié)點來說,為它們生成代碼很簡單,如下所示:
function genReturnStatement(node, context){
const {push} = context
push(`return `)
genNode(node.return, context)
}
function genStringLiteral(node, context){
const {push} = context
// 對于字符串字面量,只需要追加與 node.value 對應的字符串即可
genNode(`'${node.value}'`)
}
最后,只剩下genCallExpression 函數(shù)了,實現(xiàn)如下:
function genCallExpression(node, context){
const {push} = context
// 取得被調(diào)用函數(shù)名稱和參數(shù)列表
const {callee, arguments: args} = node
// 生成函數(shù)調(diào)用代碼
push(`${callee.name}`)
genNodeList(args,context)
push(`)`)
}
配合上述生成器函數(shù)的實現(xiàn),就能得到符合語氣的渲染函數(shù)代碼,用下面的代碼測試
const ast = parse(`<div><p>Vue</p><p>Template</p></div>`) transform(ast) const code = generate(ast.jsNode)
最終得到的代碼字符串如下:
function render (){
return h('div',[h('p','Vue'), h('p','Template')])
}
到此這篇關(guān)于Vue編譯器實現(xiàn)代碼生成方法介紹的文章就介紹到這了,更多相關(guān)Vue代碼生成內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue-router路由參數(shù)刷新消失的問題解決方法
本篇文章主要介紹了vue-router路由參數(shù)刷新消失的問題解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06

