ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] # 装饰者模式 给对象动态地增加职责的方式称为装饰者模式。 装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式。 ## 模拟传统面向对象语言的装饰者模式 ```javascript var Plan = function(){}; Plan.prototype.fire = function(){ console.log('普通子弹'); }; var MissileDecorator = function(plan){ this.plan = plan; }; MissileDecorator.prototype.fire = function(){ this.plan.fire(); console.log('导弹'); }; var AtomDecorator = function(plan){ this.plan = plan; }; AtomDecorator.prototype.fire = function(){ this.plan.fire(); console.log('原子弹'); }; var plan = new Plan(); plan = new MissileDecorator(plan); plan = new AtomDecorator(plan); plan.fire(); ``` 这种给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口,当请求到达链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的下一个对象。 ## 装饰者也是包装器 装饰者模式将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。 ## 回到JavaScript的装饰者 ```javascript var plan = { fire: function(){ console.log('普通子弹'); } }; var missilDecorator = function(){ console.log('导弹'); }; var atomDecorator = function(){ console.log('原子弹'); }; var fire1 = plane.fire; plan.fire = function(){ fire1(); missilDecorator(); }; var fire2 = plan.fire; plan.fire = function(){ fire2(); atomDecorator(); }; plan.fire(); ``` ## 装饰函数 通过保存原引用的方式就可以改写某个函数 ```javascript var a = function(){ alert(1); }; var _a = a; a = function(){ _a(); alert(2); }; a(); ``` 但是这种方式存在两个问题: - 必须维护一个中间变量,如果装饰函数的装饰链较长,或者需要装饰的函数变多,这些中间变量的数量也会越来越多。 - 还会遇到`this`被劫持的问题。 ## 用AOP装饰函数 给出`Function.prototype.before`和`Function.prototype.after`方法 ```javascript Function.prototype.before = function(beforeFn){ var _self = this; // 保存原函数的引用 return function(){ // 返回包含了原函数和新函数的“代理”函数 beforefn.apply(this, arguments); // 执行新函数,且保证this不被劫持,新函数接受的参数也会原封不动地传入原函数,新函数在原函数之前执行 return _self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,并且保证this不被劫持 } }; Function.prototype.after = function(afterFn){ var _self = this; return function(){ var ret = _self.apply(this, arguments); afterFn.apply(this, arguments); return ret; } }; ``` `Function.prototype.before`接受一个函数当作参数,这个函数即为新添加的函数,它装载了新添加的功能。 接下来把当前的`this`保存起来,这个`this`指向原函数,然后返回一个“代理”函数,这个“代理”函数只是结构上像代理而已,并不承担代理的职责。它的工作是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序,让新添加的函数在原函数之前执行,这样就实现了动态装饰的效果。 ## AOP的应用实例 用`AOP`装饰函数的技巧在实际开发中非常有用。不论是业务代码的编写,还是在框架层面,都可以把行为依照职责分成粒度更细的函数,随后通过装饰把它们合并到一起,有助于编写一个松耦合和高复用性的系统。 ### 数据统计上报 ```javascript Function.prototype.after = function(afterFn){ var _self = this; return function(){ var ret = _self.apply(this, arguments); afterFn.apply(this, arguments); return ret; } }; var showLogiun = function(){ console.log('打开登录浮层'); }; var log = function(){ console.log('上报数据'); }; showLogin = showLogin.after(log); document.getElementById('login').onclick = showLogin; ``` ### 用AOP动态改变函数的参数 ```javascript Function.prototype.before = function(beforeFn){ var _self = this; return function(){ beforefn.apply(this, arguments); return _self.apply(this, arguments); } }; var func = function(param){ console.log(param); // 输出:{a: 'a', b:'b'} }; func = func.before(function(param){ param.b = 'b'; }); func({a: 'a'}); ``` ### 插件式的表单验证 ```javascript Function.prototype.before = function(beforeFn){ var _self = this; return function(){ if(beforefn.apply(this, arguments) === false){ return; }; return _self.apply(this, arguments); } }; var validata = function(){ if(username.value === ''){ alert('不能为空'); return false; } if(password.value === ''){ alert('不能为空'); return false; } }; var formSubmit = function(){ var param = { username: username.value, password: password.value }; ajax('....'); }; formSubmit = formSubimt.before(validata); submitBtn.onclick = function(){ formSubmit(); }; ``` 函数通过`Function.prototype.before`或者`Function.prototype.after`被装饰之后,返回的实际上是一个新的函数,如果在原函数上保存了一些属性,那么这些属性会丢失。 这种装饰方式也叠加了函数的作用域,如果装饰的链条过长,性能上也会受到一些影响。