1. 概念
AOP - Aspect oriented programming 面向切面编程。把一些跟核心业务逻辑模块无关的功能抽离出来,再通过“动态织入”的方式掺入业务逻辑模块中。
主要有以下几类使用场景:
- 分离业务代码和统计代码 (日志、埋点等)
- 分离表单校验和提交
- 参数校验与更改
- 性能分析、统计函数执行时间
- 不更改其他人代码的基础上增加自己的功能逻辑。
2. Function.prototype.before 和 Function.prototype.after
其实所有的需求都可以概括为一类,那就是在主代码逻辑之前,或之后增加一个方法,这个方法可以处理与主代码无关的其他逻辑。那么可以在Function的原型上挂载两个方法,每个需要AOP的函数执行前通过这两个方法重新包裹以下,就可以达到想要的目的。
Function.prototype.before = function (beforeFn) {
//存储当前this
const self = this;
return function () {
//调用主函数前先调用before函数,可以执行统计、埋点操作
//传递参数,可以进行参数的检验与修改
beforeFn.apply(this, arguments);
//执行主函数,并返回结果
return self.apply(this, arguments);
}
}
Function.prototype.after = function (afterFn) {
const self = this;
return function () {
//与before方法类似,只是先调用了主函数
let ret = self.apply(this, arguments);
afterFn.apply(this, arguments);
return ret;
}
}
function mainFn (options) {
console.log(`执行了main, ${options.name}, ${options.age}`);
}
function beforeFn (options) {
options.name = options.name || 'nan';
options.age = options.age || 18;
console.log(`执行了before`);
}
function afterFn () {
console.log(`执行了after`);
}
mainFn = mainFn.before(beforeFn).after(afterFn);
mainFn({name: 'nan'});
// 执行了before
// 执行了main, nan, 18
// 执行了after
before和after其实就是在原型上定义一个高阶函数,在返回的函数里面按照顺序去执行相应的逻辑。 在调用主函数的时候先去调用原型上的before和after方法,重新赋给主函数。
3. ES6 装饰器
AOP在ES6中可以用装饰器实现。
function before(beforeFn = function () {}) {
return function (target, name, descriptor) {
let oldValue = descriptor.value;
descriptor.value = function () {
beforeFn.apply(this, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}
}
function beforeLog() {
console.log('beforeLog');
}
class Main {
constructor(val) {
this.val = val;
}
@before(beforeLog)
getVal() {
console.log('Main getVal')
return this.val;
}
}
方法上的装饰器接受3个参数,
- target 类原型 Main.prototype
- name 要装饰的属性名 getVal
- descriptor 该属性的描述对象
{
value: getValFunction,
enumerable: false,
configurable: true,
writable: true
}