1.什么是AOP

AOP(Aspect-Oriented Programming):面向切面编程,是对 OOP 的补充。

AOP的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后, 再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处是一方面保持了业务逻辑模块的纯净性,也提高业务无关模块功能的复用性,有助于编写一个松耦合高复用性的系统。

2.如何实现AOP

在 JavaScript 中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中,也就是当执行一个核心业务逻辑的函数时,也静默的执行一下跟业务逻辑无关的函数,这个跟业务逻辑无关的函数可以放在核心业务逻辑的函数之前执行,也可以在其之后执行,这样也就有了beforeafter函数,具体的实现技术有很多,我们通过扩展 Function.prototype 来做到这一点。

  1. Function.prototype.before = function (beforefn) {
  2. var self = this; // 保存原函数的引用
  3. return function () { // 返回包含了原函数和新函数的"代理"函数
  4. beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数 // 也会被原封不动地传入原函数,新函数在原函数之前执行
  5. return self.apply(this, arguments); // 执行原函数并返回原函数的执行结果, 2 // 并且保证 this 不被劫持
  6. }
  7. }
  8. Function.prototype.after = function (afterfn) {
  9. var self = this;
  10. return function () {
  11. var ret = self.apply(this, arguments); afterfn.apply(this, arguments);
  12. return ret;
  13. }
  14. };
  15. // demo
  16. function log1() {
  17. console.log(1);
  18. }
  19. function log2() {
  20. console.log(2);
  21. }
  22. function log3() {
  23. console.log(3);
  24. }
  25. log2.before(log1).after(log3)();

上面的 AOP 实现是在 Function.prototype 上添加 before 和 after 方法,但许 多人不喜欢这种污染原型的方式,那么我们可以做一些变通,把原函数和新函数都作为参数传入 before 或者 after 方法:

  1. function before(fn, beforeFn) {
  2. return function (...args) {
  3. beforeFn.apply(this, args);
  4. return fn.apply(this, args);
  5. };
  6. }
  7. function after(fn, afterFn) {
  8. return function (...args) {
  9. const ret = fn.apply(this, args);
  10. afterFn.apply(this, args);
  11. return ret;
  12. };
  13. }
  14. // demo
  15. function log1() {
  16. console.log(1);
  17. }
  18. function log2() {
  19. console.log(2);
  20. }
  21. function log3() {
  22. console.log(3);
  23. }
  24. after(before(log2, log1), log3)();

3.AOP的应用实例

用 AOP 装饰函数的技巧在实际开发中非常有用。

数据统计上报

分离业务代码和数据统计代码,无论在什么语言中,都是 AOP 的经典应用之一。
比如页面中有一个登录 button,点击这个 button 会弹出登录浮层,与此同时要进行数据上报, 来统计有多少用户点击了这个登录 button:

  1. <html>
  2. <button tag="login" id="button">点击打开登录浮层</button>
  3. <script>
  4. var showLogin = function () {
  5. console.log('打开登录浮层');
  6. log(this.getAttribute('tag'));
  7. };
  8. var log = function (tag) {
  9. console.log('上报标签为: ' + tag);
  10. };
  11. document.getElementById('button').onclick = showLogin;
  12. </script>
  13. </html>

我们看到在 showLogin 函数里,既要负责打开登录浮层,又要负责数据上报,这是两个层面 的功能,在此处却被耦合在一个函数里。使用 AOP 分离之后,代码如下:

  1. <html>
  2. <button tag="login" id="button">点击打开登录浮层</button>
  3. <script>
  4. Function.prototype.after = function (afterfn) {
  5. var self = this;
  6. return function () {
  7. var ret = self.apply(this, arguments);
  8. afterfn.apply(this, arguments);
  9. return ret;
  10. };
  11. };
  12. var showLogin = function () {
  13. console.log('打开登录浮层');
  14. };
  15. var log = function () {
  16. console.log('上报标签为: ' + this.getAttribute('tag'));
  17. };
  18. showLogin = showLogin.after(log); // 打开登录浮层之后上报数据
  19. document.getElementById('button').onclick = showLogin;
  20. </script>
  21. </html>

参考文献
《JavaScript设计模式与开发实践》3.2