aop - 切面编程,主要用于执行一些与业务不相关的操作。
比如上报数据、日志输出等与业务不强相关的操作,把这些非业务操作与业务流程解耦。
还有其它使用情况,比如有一个通用弹层,打开弹层和关闭弹层都有一些操作,比如打开弹层需要把body的颜色变成灰色,然后关闭弹层需要把颜色还原等这样的操作。正常的解决方法是
需要增加beforeOpen,beforeClose这样的回调函数,需要接口提供对应的参数接口,如果需要在打开弹层之后还需要提供 afterOpen等这样的方法。但是如果弹层支持aop就可以直接对open方法进行修改了。
一般情况下,aop是用在同步函数中使用的,但是有时候异步函数也需要用到,比如:请求列表的数据,需要把数据列表的盒展示成loading状态,数据请求回来之后,再去掉loading状态。所以我写了个工具函数,也支持
返回Promise的函数、async函数、Generator函数。
aop通用方法完整案例如下:
export function isFunction(arg) { return typeof arg === "function"; } export function isGeneratorFunction(obj) { return obj && obj.constructor && "GeneratorFunction" === obj.constructor.name; } export function isAsyncFunction(obj) { return obj && obj.constructor && "AsyncFunction" === obj.constructor.name; } export function isPromise(obj) { return obj && "function" === typeof obj.then; } /** * 切面编程 * @param {Function | GeneratorFunction | AsyncFunction} fn - 方法 * @param {*} beforeFn - 前置方法 * @param {*} afterFn - 后置方法 */ export function aop(fn, beforeFn, afterFn) { if (isGeneratorFunction(fn)) { const newFn = function(...args) { const me = this; if (isFunction(beforeFn)) { beforeFn.apply(me, args); } let ret = fn.apply(me, args); let next = ret.next; ret.next = function(...arg) { const done = next.apply(ret, arg); if (done.done) { if (isFunction(afterFn)) { afterFn.apply(me, args); } } return done; }; return ret; }; for (let key in fn) { if (fn.hasOwnProperty(key)) { newFn[key] = fn[key]; } } return newFn; } if (isAsyncFunction(fn)) { const newFn = async function(...args) { const me = this; if (isFunction(beforeFn)) { beforeFn.apply(me, args); } return await fn .apply(me, args) .then((...data) => { if (isFunction(afterFn)) { afterFn.apply(me, data); } return data; }) .catch((...err) => { if (isFunction(afterFn)) { afterFn.apply(me, err); } throw err; }); }; for (let key in fn) { if (fn.hasOwnProperty(key)) { newFn[key] = fn[key]; } } return newFn; } if (isFunction(fn)) { const newFn = function(...args) { const me = this; if (isFunction(beforeFn)) { beforeFn.apply(me, args); } const ret = fn.apply(me, args); if (isFunction(afterFn)) { if (isPromise(ret)) { ret .then((...data) => { afterFn.apply(me, data); }) .catch((...err) => { afterFn.apply(me, err); }); } else { afterFn.apply(me, args); } } return ret; }; for (let key in fn) { if (fn.hasOwnProperty(key)) { newFn[key] = fn[key]; } } return newFn; } } export function before(fn, callback) { return aop(fn, callback, null); } export function after(fn, callback) { return aop(fn, null, callback); } export function wrap(fn, beforeFn, afterFn) { return aop(fn, beforeFn, afterFn); } export function aopFactory(fn) { //非函数或者已有aop方法原样输出 if (typeof fn !== "function" || fn.before || fn.after || fn.wrap) { return fn; } fn.before = function(callback) { const me = this; return before(me, callback); }; fn.after = function(callback) { const me = this; return after(me, callback); }; fn.wrap = function(beforeFn, afterFn) { const me = this; return wrap(me, beforeFn, afterFn); }; return fn; }
使用案例 - 普通方法调用:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>普通函数</title> </head> <body> <script src="./aop.js"></script> <script> function pureFn() { console.log('我是一个普通的函数'); } const aopFn = aopFactory(pureFn); const fn = aopFn.before(function () { console.log('我是前置方法') }) .after(function () { console.log('我是后置方法') }); fn(); </script> </body> </html>
使用案例 - async方法调用 :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>普通函数</title> </head> <body> <script src="./aop.js"></script> <script> async function asyncFn() { const ret = await new Promise((resolve) => { setTimeout(() => { console.log('异步函数内部操作') resolve('我是async方法,返回值。') }, 1000); }) console.log(`ret: ${ret}`) return ret; } const aopFn = aopFactory(asyncFn); const fn = aopFn.before(function () { console.log('我是前置方法') }) .after(function () { console.log('我是后置方法') }); fn() .then((data) => { console.log(data); }) .finally(() => { console.log('finally'); }) </script> </body> </html>
结果:
使用案例 - promise方法 :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>普通函数</title> </head> <body> <script src="./aop.js"></script> <script> function promiseFn() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('返回内容') }, 1000) }) } const aopFn = aopFactory(promiseFn); const fn = aopFn.before(function () { console.log('前置方法') }) .after(function () { console.log('后置方法') }); fn().then((data) => { console.log(data); }); </script> </body> </html>
注意这里有个小问题是,这个after的方法是在异步之后执行的,但是是在其它then之前执行的。