Vue編譯器實(shí)現(xiàn)代碼生成方法介紹
這里將討論如何根據(jù)JavaScript AST生成渲染函數(shù)的代碼,即代碼生成。代碼生成本質(zhì)上是字符串拼接的藝術(shù)。需要訪問(wèn)JavaScript AST中的節(jié)點(diǎn),為每一種類型的節(jié)點(diǎn)生成相符的 JavaScript代碼。
本節(jié),將實(shí)現(xiàn) generate函數(shù)來(lái)完成代碼生成的任務(wù)。
function compile(template) { //模板 AST const ast = parse(template) //將模板 AST轉(zhuǎn)換為JavaScript AST transform(ast) // 代碼生成 const code = generate(ast.jsNode) return code }
在這里,代碼生成也需要上下文對(duì)象。
function generate(node){ const context = { // 存儲(chǔ)最終生成的渲染函數(shù)代碼 code: '', // 在生成代碼時(shí),通過(guò)調(diào)用 push 函數(shù)完成代碼的拼接 push(code){ context.code += code } } // 該方法完成代碼生成的工作 genNode(node,context) // 返回渲染函數(shù)代碼 return context.code }
另外,為了讓最終生成的代碼具有較強(qiáng)的可讀性,要考慮生成代碼的格式,這就需要擴(kuò)展context對(duì)象,為其增加用來(lái)完成換行和縮進(jìn)的工具函數(shù),如下面代碼所示:
function generate(node){ const context = { code: '', push(code){ context.code += code }, // 當(dāng)前縮進(jìn)的級(jí)別,初始值為 0,即沒(méi)有縮進(jìn) currentIndent: 0, // 該函數(shù)用來(lái)?yè)Q行,即在代碼字符串的后面追加 \n 字符 // 另外,換行時(shí)應(yīng)該保留縮進(jìn),所以還要追加 currentIndent * 2 個(gè)空格字符 newline(){ context.code += '\n'+` `.repeat(context.currentIndent) }, // 用來(lái)縮進(jìn),即讓 currentIndent 自增后,調(diào)用換行函數(shù) indent(){ context.currentIndent++ context.newline() }, // 取消縮進(jìn) deIndent(){ context.currentIndent-- context.newline() }, } genNode(node, context) return context.code }
有了這些基礎(chǔ)能力之后,就可以開(kāi)始編寫(xiě) genNode 函數(shù)來(lái)完成代碼生成的工作了。代碼生成的原理其實(shí)很簡(jiǎn)單,只需要匹配各種類型的 JavaScript AST節(jié)點(diǎn),并調(diào)用對(duì)應(yīng)的生成函數(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é)點(diǎn)類型,只需要再genNode函數(shù)中添加相應(yīng)的處理分支即可
接下來(lái)逐步完善代碼生成工作,首先實(shí)現(xiàn)函數(shù)生命語(yǔ)句的代碼生成,即genFunctionDecl函數(shù),如下面的代碼所示:
function genFunctionDecl(node,context){ // 從context 對(duì)象中取出工具函數(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ù)用來(lái)為函數(shù)聲明類型的節(jié)點(diǎn)生成對(duì)應(yīng)的 JavaScript代碼。以聲明節(jié)點(diǎn)為例,它最終生成的代碼將會(huì)是:
function render(){ ...函數(shù)體 }
genNodeList函數(shù)的實(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(',') } } }
這里要注意的一點(diǎn)是,每處理完一個(gè)節(jié)點(diǎn),需要在生成的代碼后面拼接逗號(hào)。
實(shí)際上,genArrayExpression函數(shù)就利用了這個(gè)特點(diǎn)來(lái)實(shí)現(xiàn)對(duì)數(shù)組表達(dá)式的代碼生成,如下面的代碼所示:
function genArrayExpression(node, context){ const {push} = context // 追加方括號(hào) push(`[`) genNodeList(node.elements, context) // 補(bǔ)全方括號(hào) push(`]`) }
對(duì)于 genFunctionDecl 函數(shù),另外需要注意的是,由于函數(shù)體本身也是一個(gè)節(jié)點(diǎn)數(shù)組,所以需要遍歷它并遞歸地調(diào)用 genNode 函數(shù)生成代碼。
對(duì)于ReturnStatement和StringLiteral類型的節(jié)點(diǎn)來(lái)說(shuō),為它們生成代碼很簡(jiǎn)單,如下所示:
function genReturnStatement(node, context){ const {push} = context push(`return `) genNode(node.return, context) } function genStringLiteral(node, context){ const {push} = context // 對(duì)于字符串字面量,只需要追加與 node.value 對(duì)應(yīng)的字符串即可 genNode(`'${node.value}'`) }
最后,只剩下genCallExpression 函數(shù)了,實(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ù)的實(shí)現(xiàn),就能得到符合語(yǔ)氣的渲染函數(shù)代碼,用下面的代碼測(cè)試
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編譯器實(shí)現(xiàn)代碼生成方法介紹的文章就介紹到這了,更多相關(guān)Vue代碼生成內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3輸入單號(hào)和張數(shù)如何自動(dòng)生成連號(hào)的單號(hào)
最近遇到這樣的需求輸入連號(hào)事件,需要在表格中輸入物流單號(hào),物流號(hào)碼,生成的數(shù)量,名稱,點(diǎn)擊確定自動(dòng)生成固定數(shù)量的連號(hào)物流單號(hào),本文重點(diǎn)介紹vue3輸入單號(hào)和張數(shù),自動(dòng)生成連號(hào)的單號(hào),感興趣的朋友一起看看吧2024-02-02Vue必學(xué)知識(shí)點(diǎn)之forEach()的使用
在前端開(kāi)發(fā)中,經(jīng)常會(huì)遇到一些通過(guò)遍歷循環(huán)來(lái)獲取想要的內(nèi)容的情形,這時(shí)候就需要用到forEach()了,下面這篇文章主要給大家介紹了關(guān)于Vue必學(xué)知識(shí)點(diǎn)之forEach()使用的相關(guān)資料,需要的朋友可以參考下2021-05-05springboot+vue實(shí)現(xiàn)文件上傳下載
這篇文章主要為大家詳細(xì)介紹了springboot+vue實(shí)現(xiàn)文件上傳下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11Vue.js組件實(shí)現(xiàn)選項(xiàng)卡以及切換特效
這篇文章主要為大家詳細(xì)介紹了Vue.js組件實(shí)現(xiàn)選項(xiàng)卡以及切換特效,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07vue-router路由參數(shù)刷新消失的問(wèn)題解決方法
本篇文章主要介紹了vue-router路由參數(shù)刷新消失的問(wèn)題解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06vue實(shí)現(xiàn)翻牌動(dòng)畫(huà)
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)翻牌動(dòng)畫(huà),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04