詳解使用抽象語法樹AST實現(xiàn)一個AOP切面邏輯
開篇
AST 功能很靈活,可以通過改變一些自定義結(jié)構(gòu)便可以輸入自定義的功能,下面簡單的展示下如何利用抽象語法樹AST實現(xiàn)一個AOP切面邏輯
一、實現(xiàn)目的
將 js 文件下的全部方法添加一個開始事件和一個結(jié)束事件并傳入方法名,以便監(jiān)聽某個方法的調(diào)用。
1、work.js
function eat(){ let food = "apple" let time = '2小時' return '吃' + food + '用時' + time } function work(){ } console.log(eat())
控制臺打印
可以看到,輸入了執(zhí)行 eat 方法的開始事件及結(jié)束事件。其實,這個過程就是 work.js 轉(zhuǎn)成語法樹后,對方法節(jié)點進行了處理,添加了兩個切面方法:start 和 end 事件。
2、aop.js
這個文件用來聲明 start 和 end 事件。
function start(funcName){ console.log('開始執(zhí)行:' + funcName + '方法') } function end(funcName){ console.log('執(zhí)行結(jié)束:' + funcName + '方法') }
二、利用語法樹添加切面事件
//文件操作工具 const fs = require('fs'); //JS代碼轉(zhuǎn)語法樹工具 const parser = require('./babel-core/node_modules/babylon'); //語法樹遍歷工具 const traverse = require('./babel-core/node_modules/babel-traverse'); //語法樹轉(zhuǎn)JS代碼工具 const generator = require('./babel-core/node_modules/babel-generator'); //聲明語法樹類型工具 const t = require('./babel-core/node_modules/babel-types'); //獲取aop代碼 let aop = fs.readFileSync('./aop.js','utf-8') //獲取需要添加切面的代碼 let content = fs.readFileSync('./work.js','utf-8') //將需要添加切面的代碼轉(zhuǎn)換為語法樹 let ast = parser.parse(content,{ sourceType:'script' }) //遍歷指定語法樹時操作項(這里遍歷的指定節(jié)點是FunctionDeclaration方法) const visitor = { FunctionDeclaration:({ node }) => { //獲取每個方法體里面的節(jié)點 let funcBody = node.body.body //創(chuàng)建一個名為start,參數(shù)為當(dāng)前方法名的執(zhí)行節(jié)點,后面的參數(shù)為創(chuàng)建一個名為start方法的參數(shù) let start = t.callExpression(t.identifier('start'), [t.identifier(`"${node.id.name}"`)]) //添加到方法的最前面 funcBody.unshift(start) //判斷最后一個節(jié)點是不是return,方式結(jié)束事件調(diào)用節(jié)點在最后無法調(diào)用。 let lastNode = funcBody.slice(-1) //創(chuàng)建一個名為end,參數(shù)為當(dāng)前方法名的執(zhí)行節(jié)點,后面的參數(shù)為end方法的參數(shù) let end = t.callExpression(t.identifier('end'), [t.identifier(`"${node.id.name}"`)]) //設(shè)定end節(jié)點添加的位置 let insertEndEventPosition = (funcBody.length) if(lastNode[0].type == 'ReturnStatement'){ //放在return節(jié)點的前面 insertEndEventPosition -= 1 } //添加end節(jié)點 funcBody.splice(insertEndEventPosition,0,end) } } //開始遍歷操作語法樹 traverse.default(ast,visitor) //將處理完的語法樹再次轉(zhuǎn)換為JS代碼 let codeResult = generator.default(ast) //這里需要添加aop里面的兩個切面事件到最終的JS代碼里。 let outFileCode = aop + '\n\n' + codeResult.code // 寫入文件操作 fs.mkdir('cache',(err)=>{ if(!err){ fs.writeFile('cache/main.js',outFileCode,(err)=>{ if(!err){ console.log('文件創(chuàng)建完成') } }) } })
上面的代碼執(zhí)行完后,看下 main.js 生成的代碼內(nèi)容。
function start(funcName){ console.log('開始執(zhí)行:' + funcName + '方法') } function end(funcName){ console.log('執(zhí)行結(jié)束:' + funcName + '方法') } function eat() { start("eat") let food = "apple"; let time = '2小時'; end("eat") return '吃' + food + '用時' + time; } function work() { start("work") end("work") } console.log(eat());
相比較之前的 work.js 文件,添加了兩個切面方法的同時,保證每個方法都添加了 start 及 end 事件。這樣再調(diào)用的時候便可直接在執(zhí)行前后操作其他任務(wù)了。
三、總結(jié)與思考
其實上面的操作人工也可以直接操作,但是,將一切重復(fù)單一的工作交給程序,這或許是代碼存在的真正意義。一個簡單的例子或許能打開一個新的世界,雖然,每一行代碼都是獨一無二的,但是如果追本溯源,最初并且真切的思想?yún)s是相通的
以上就是詳解使用抽象語法樹AST實現(xiàn)一個AOP切面邏輯的詳細(xì)內(nèi)容,更多關(guān)于AST抽象語法樹實現(xiàn)AOP的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Node.js?中的?module.exports?與?exports區(qū)別介紹
這篇文章主要介紹了Node.js中的module.exports與exports區(qū)別介紹,每個模塊中都有module對象,存放了當(dāng)前模塊相關(guān)的信息,更多相關(guān)內(nèi)容需要的朋友可以參考一下2022-09-09提升node.js中使用redis的性能遇到的問題及解決方法
本文中提到的node redis client采用的基于node-redis封裝的二方包,因此問題排查也基于node-redis這個模塊。接下來通過本文給大家分享提升node.js中使用redis的性能2018-10-10Node.js+jade+mongodb+mongoose實現(xiàn)爬蟲分離入庫與生成靜態(tài)文件的方法
下面小編就為大家?guī)硪黄狽ode.js+jade+mongodb+mongoose實現(xiàn)爬蟲分離入庫與生成靜態(tài)文件的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09Node.js用readline模塊實現(xiàn)輸入輸出
在學(xué)C++的時候,有cout和cin,Java也有println和Scanner控件,Node.js也有如同C++和Java的標(biāo)準(zhǔn)輸入,當(dāng)然,是用JavaScript實現(xiàn)的,它就是Readline模塊。下面這篇文章就給大家詳細(xì)介紹一下readline模塊,來實現(xiàn)Node.js的控制臺輸入輸出。有需要的可以參考借鑒。2016-12-12node.js平臺下利用cookie實現(xiàn)記住密碼登陸(Express+Ejs+Mysql)
這篇文章主要介紹了node.js平臺下利用cookie實現(xiàn)記住密碼登陸(Express+Ejs+Mysql),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04