目录

  1. 继承与装饰者模式
  2. 简单的装饰者模式实例
  3. AOP装饰函数
  4. AOP实例
    1. 数据统计上报
    2. 动态改变函数的参数
    3. 插件式的表单验证

继承与装饰者模式

在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是,继承的方式并不灵活,还会带来很多问题: 一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变; 另一方面,继承这种功能复用方式通常被称为”白盒复用”,”白盒”是相对可见性而言的,在继承方式中,超类的内部细节是对子类可见的,继承常常被认为破坏了封装性。

使用继承还会带来一个问题: 在完成一些功能复用的同时,有可能创建出大量的子类,使子类的数量呈爆炸性增长。

装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责,跟继承相比,装饰者是一种更加灵活的做法。

简单的装饰者模式实例

直接上代码:

  1. var plane = {
  2. fire: function() {
  3. console.log('发射普通子弹');
  4. }
  5. };
  6. var missileDecorator = function() {
  7. console.log('发射导弹');
  8. };
  9. var atomDecorator = function() {
  10. console.log('发射原子弹');
  11. };
  12. var fire1 = plane.fire;
  13. plane.fire = function() {
  14. fire1();
  15. missileDecorator();
  16. };
  17. var fire2 = plane.fire;
  18. plane.fire = function() {
  19. fire2();
  20. atomDecorator();
  21. };
  22. plane.fire(); // 输出: "发射普通子弹" "发射导弹" "发射原子弹"

AOP装饰函数

首先编写两个方法,分别为 Function.prototype.before 和 Function.prototype.after,内容如下:

  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); // 执行原函数并返回原函数的执行结果,并且保证this不被劫持
  6. }
  7. };
  8. Function.prototype.after = function(afterFn) {
  9. var _self = this;
  10. return function() {
  11. var ret = _self.apply(this, arguments);
  12. afterFn.apply(this, arguments);
  13. return ret;
  14. }
  15. };

Function.prototype.before 接收一个函数作为参数,这个函数即为新添加的函数,它装载了新添加的功能代码,返回的是一个”代理”函数(只是结构上像代理),它的工作是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序,让新添加的函数在原函数之前执行(这称为”前置装饰”),这样就实现了动态装饰的效果。Function.prototype.after 与 Function.prototype.before 原理相同,只不过是新函数与原函数的执行顺序相反而已。

使用实例(window.onload实例):

  1. window.onload = function() {
  2. alert(1);
  3. };
  4. window.onload = (window.onload || function() {}).after(function() {
  5. alert(2);
  6. }).after(function() {
  7. alert(3);
  8. }).after(function() {
  9. aalert(4);
  10. });

AOP实例

数据统计上报

在项目开发的结尾阶段难免要加上很多统计数据的代码,这些过程可能让我们被迫改动早已封装好的函数,使用 AOP,可以避免这种情况,直接上代码:

  1. ...
  2. // 业务逻辑
  3. var showLogin = function() {
  4. console.log('打开登录浮层');
  5. };
  6. // 数据上报部分逻辑
  7. var log = function() {
  8. console.log('上报标签为: ' + this.getAttribute('tag'));
  9. };
  10. showLogin = showLogin.after('log');
  11. document.getElementById('button').onclick = showLogin;

动态改变函数的参数

假如我们有一套旧的 ajax 异步请求机制,原本运行正常,然而有一天,我们的页面遭受了 CSRF 攻击,解决 CSRF 攻击最简单的方法是: 在 Http 请求中加入一个 token 参数,如果冒然将这个机制加入到原有的 ajax 请求代码内部,将会导致这块代码可复用性降低。使用 AOP 可以解决这个问题,直接上代码:

  1. ...
  2. var ajax = function(type, url, param) {
  3. ...
  4. // 省略发起ajax请求的过程
  5. console.log(param);
  6. };
  7. var getToken = function() {
  8. return 'Token';
  9. };
  10. // 在ajax请求发起前加入一个token参数
  11. ajax = ajax.before(function(type, url, param) {
  12. param.token = getToken();
  13. });
  14. ajax('get', 'http://www.xxx.com/api', { name: 'WJT20' });

插件式的表单验证

表单验证是将表单数据提交服务器前的必需操作,常用的表单验证主要有是否为空判断、填写格式是否合法和填写信息字长等等。表单验证和将填写数据发送服务器是两部分逻辑,很多人经常将两者混写到一块,这样就会导致代码十分臃肿,职责也很混乱。使用 AOP 则可以将这两块逻辑代码分离开来,各司其职,至少代码看起来比之前要更优雅一些。

实例代码:

  1. // 这里对Function.prototype.before方法做了一些改造
  2. Function.prototype.before = function(beforeFn) {
  3. var _self = this;
  4. return function() {
  5. if (beforeFn.apply(this, arguments) === false) {
  6. return; // beforeFn返回false的情况直接return,不再执行后面的原函数
  7. }
  8. return _self.apply(this, arguments);
  9. };
  10. }
  11. var username = document.getElementById('username'),
  12. password = document.getElementById('paddword'),
  13. submitBtn = document.getElementById('submitBtn');
  14. // 表单验证部分逻辑代码
  15. var validata = function() {
  16. if (username.value === '') {
  17. alert('用户名不能为空');
  18. return false;
  19. }
  20. if (password.value === '') {
  21. alert('密码不能为空');
  22. return false;
  23. }
  24. };
  25. // 提交信息部分逻辑代码
  26. var formSubmit = function() {
  27. var param = {
  28. username: username.value,
  29. password: password.value
  30. };
  31. ajax('http://www.xxx.com/api', param);
  32. };
  33. // 提交信息之前校验信息
  34. formSubmit = formSubmit.before(validata);
  35. submitBtn.onclick = function() {
  36. formSubmit();
  37. }

  1. ID : 62
  2. DATE : 2018/01/21
  3. AUTHER : WJT20
  4. TAG : JavaScript