利用装饰器(decorator)对vue组件中的methods实现切面(aop)编程,进行日志监控、入参校验等
近期在做一个模块,需求是对vue组件的方法调用加日志监控,于是想到了在写java的时候用到了spring的aop,达到对原代码无污染的做日志的方法,于是搜索了一些资料企图在vue中实现这个效果,在这里记录一下。
实现原理是使用ES6中新增的对象装饰器(decorator),对原对象的方法进行包装,加入想要前置执行或后置执行的方法,如写日志。
1、在vue开发中使用装饰器(decorator)
由于大多数浏览器不支持decorator,开发时需安装babel及相应的插件。
npm install babel-core babel-plugin-transform-decorators
然后配置文件.babelrc(如不存在则在项目跟目录新建)
"plugins": [["@babel/plugin-proposal-decorators",{
"legacy": true}]]
2、编写“切面”注册方法
首先编写一个基础方法,逻辑是遍历装饰的vue对象的methods(当然你也可以试试拦截其他对象),对需要加切面的方法进行包装,让方法在执行前先执行“前置方法”,执行后再执行“后置方法”。
//decorator-base.js
/*** 封装了装饰器decorator逻辑的基类,自定义装饰器通过创建此对象后改写对应属性和方法来为指定方法增加aop功能* @param filter:需要增加aop方法名数组,如果只需要对部分方法起效就重写这个属性* @param doBefore(fn,args):在执行原方法前要执行的逻辑,需返回一个bool值决定程序是否执行,默认返回true;* fn对象是封装后的function对象,添加了自定义属性aspectInfo方便获取一些组件信息,结构如下:* {targetName:组件名,funcName:方法名}* @param doAfter(fn,args,result):在执行原方法后要执行的逻辑。* @returns {*}*/
function decoratorBase(filter, doBefore, doAfter) {
return function (target, funName, descriptor) {
let oriMethods = Object.assign({
}, target.methods),oriTarget = Object.assign({
}, target);if (target.methods) {
for (let name in target.methods) {
if (filter && (filter[0] == '*' || filter.indexOf(name) > -1)) {
let fn = function fn(...arg) {
if (doBefore && !doBefore(fn, arg)) {
return null;}let result = oriMethods[name].call(this, ...arg);if (result instanceof Promise) {
result.then(function () {
doAfter(fn, arg, result);return result})}doAfter && doAfter(fn, arg, result);return result;}fn.aspectInfo = {
targetName: target.name,funcName: name}target.methods[name] = fn;}}}return descriptor;}
}
然后创建一个“切面”定义类用来统一定义我们的“切面”。
//decorator.jsimport decoratorBase from './decorator-base.js'export function log(methods) {
let bfn = decoratorBase([methods], function (fn, args) {
console.log(`我在${
fn.aspectInfo.targetName}--${
fn.aspectInfo.funcName}方法前执行${
args}`)return true;// console.log(`${fn.aspectInfo.funcName}方法被拦截,不会往下走`)// return false;}, function (fn, args, result) {
console.log(`我在${
fn.aspectInfo.targetName}--${
fn.aspectInfo.funcName}方法后执行${
args},结果是${
result}`)})return bfn;
}
3、在vue对象中使用已注册的“切面”
对一个测试方法login做一下日志
@log("login")methods: {
login() {
if (localStorage.getItem("name") === this.name &&localStorage.getItem("password") === this.password) {
this.name = "";this.password = "";this.$router.push("/home/list");} else {
alert("账号密码不正确");}},