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;}};// demofunction 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;};}// demofunction 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
