1.什么是AOP
AOP(Aspect-Oriented Programming):面向切面编程,是对 OOP 的补充。
AOP的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后, 再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处是一方面保持了业务逻辑模块的纯净性,也提高业务无关模块功能的复用性,有助于编写一个松耦合和高复用性的系统。
2.如何实现AOP
在 JavaScript 中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中,也就是当执行一个核心业务逻辑的函数时,也静默的执行一下跟业务逻辑无关的函数,这个跟业务逻辑无关的函数可以放在核心业务逻辑的函数之前执行,也可以在其之后执行,这样也就有了before
和after
函数,具体的实现技术有很多,我们通过扩展 Function.prototype 来做到这一点。
Function.prototype.before = function (beforefn) {
var self = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的"代理"函数
beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数 // 也会被原封不动地传入原函数,新函数在原函数之前执行
return self.apply(this, arguments); // 执行原函数并返回原函数的执行结果, 2 // 并且保证 this 不被劫持
}
}
Function.prototype.after = function (afterfn) {
var self = this;
return function () {
var ret = self.apply(this, arguments); afterfn.apply(this, arguments);
return ret;
}
};
// demo
function log1() {
console.log(1);
}
function log2() {
console.log(2);
}
function log3() {
console.log(3);
}
log2.before(log1).after(log3)();
上面的 AOP 实现是在 Function.prototype 上添加 before 和 after 方法,但许 多人不喜欢这种污染原型的方式,那么我们可以做一些变通,把原函数和新函数都作为参数传入 before 或者 after 方法:
function before(fn, beforeFn) {
return function (...args) {
beforeFn.apply(this, args);
return fn.apply(this, args);
};
}
function after(fn, afterFn) {
return function (...args) {
const ret = fn.apply(this, args);
afterFn.apply(this, args);
return ret;
};
}
// demo
function log1() {
console.log(1);
}
function log2() {
console.log(2);
}
function log3() {
console.log(3);
}
after(before(log2, log1), log3)();
3.AOP的应用实例
数据统计上报
分离业务代码和数据统计代码,无论在什么语言中,都是 AOP 的经典应用之一。
比如页面中有一个登录 button,点击这个 button 会弹出登录浮层,与此同时要进行数据上报, 来统计有多少用户点击了这个登录 button:
<html>
<button tag="login" id="button">点击打开登录浮层</button>
<script>
var showLogin = function () {
console.log('打开登录浮层');
log(this.getAttribute('tag'));
};
var log = function (tag) {
console.log('上报标签为: ' + tag);
};
document.getElementById('button').onclick = showLogin;
</script>
</html>
我们看到在 showLogin 函数里,既要负责打开登录浮层,又要负责数据上报,这是两个层面 的功能,在此处却被耦合在一个函数里。使用 AOP 分离之后,代码如下:
<html>
<button tag="login" id="button">点击打开登录浮层</button>
<script>
Function.prototype.after = function (afterfn) {
var self = this;
return function () {
var ret = self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
};
};
var showLogin = function () {
console.log('打开登录浮层');
};
var log = function () {
console.log('上报标签为: ' + this.getAttribute('tag'));
};
showLogin = showLogin.after(log); // 打开登录浮层之后上报数据
document.getElementById('button').onclick = showLogin;
</script>
</html>
参考文献
《JavaScript设计模式与开发实践》3.2