当咱们拍了一张照片准备发朋友圈时,许多小伙伴会选择给照片加上滤镜。同一张照片、不一样的滤镜组合起来就会有不一样的体验。这里实际上就应用了装饰者模式:是经过滤镜装饰了照片。在不改变对象(照片)的状况下动态的为其添加功能(滤镜)。javascript
须要注意的是:因为 JavaScript 语言动态的特性,咱们很容易就能改变某个对象(JavaScript 中函数是一等公民)。可是咱们要尽可能避免直接改写某个函数,这会致使代码的可维护性、可扩展性变差,甚至会污染其余业务。html
想必你们对"餐前洗手、饭后漱口"都不陌生。这句标语其实就是 AOP 在生活中的例子:吃饭这个动做至关于切点,咱们能够在这个切点前、后插入其它如洗手等动做。vue
AOP(Aspect-Oriented Programming):面向切面编程,是对 OOP 的补充。利用AOP能够对业务逻辑的各个部分进行隔离,也能够隔离业务无关的功能好比日志上报、异常处理等,从而使得业务逻辑各部分之间的耦合度下降,提升业务无关的功能的复用性,也就提升了开发的效率。java
在 JavaScript 中,咱们能够经过装饰者模式来实现 AOP,可是二者并非一个维度的概念。 AOP 是一种编程范式,而装饰者是一种设计模式。typescript
了解了装饰者模式和 AOP 的概念以后,咱们写一段可以兼容 ES3 的代码来实现装饰者模式:编程
// 原函数 var takePhoto =function(){ console.log('拍照片'); } // 定义 aop 函数 var after=function( fn, afterfn ){ return function(){ let res = fn.apply( this, arguments ); afterfn.apply( this, arguments ); return res; } } // 装饰函数 var addFilter=function(){ console.log('加滤镜'); } // 用装饰函数装饰原函数 takePhoto=after(takePhoto,addFilter); takePhoto();
这样咱们就实现了抽离拍照与滤镜逻辑,若是之后须要自动上传功能,也能够经过aop
函数after
来添加。设计模式
在 ES5 中引入了Object.defineProperty
,咱们能够更方便的给对象添加属性:app
let takePhoto = function () { console.log('拍照片'); } // 给 takePhoto 添加属性 after Object.defineProperty(takePhoto, 'after', { writable: true, value: function () { console.log('加滤镜'); }, }); // 给 takePhoto 添加属性 before Object.defineProperty(takePhoto, 'before', { writable: true, value: function () { console.log('打开相机'); }, }); // 包装方法 let aop = function (fn) { return function () { fn.before() fn() fn.after() } } takePhoto = aop(takePhoto) takePhoto()
咱们知道,在 JavaScript 中,函数也好,类也好都有着本身的原型,经过原型链咱们也可以很方便的动态扩展,如下是基于原型链的写法:异步
class Test { takePhoto() { console.log('拍照'); } } // after AOP function after(target, action, fn) { let old = target.prototype[action]; if (old) { target.prototype[action] = function () { let self = this; fn.bind(self); fn(handle); } } } // 用 AOP 函数修饰原函数 after(Test, 'takePhoto', () => { console.log('添加滤镜'); }); let t = new Test(); t.takePhoto();
在 ES7 中引入了@decorator 修饰器的提案,参考阮一峰的文章)。修饰器是一个函数,用来修改类的行为。目前Babel转码器已经支持。注意修饰器只能装饰类或者类属性、方法。三者的具体区别请参考 MDN Object.defineProperty ;而 TypeScript 的实现又有所不一样:TypeScript Decorator。async
接下来咱们经过修饰器来实现对方法的装饰:
function after(target, key, desc) { const { value } = desc; desc.value = function (...args) { let res = value.apply(this, args); console.log('加滤镜') return res; } return desc; } class Test{ @after takePhoto(){ console.log('拍照') } } let t = new Test() t.takePhoto()
能够看到,使用修饰器的代码很是简洁明了。
装饰者模式能够应用在不少场景,典型的场景是记录某异步请求请求耗时的性能数据并上报:
function report(target, key, desc) { const { value } = desc; desc.value = async function (...args) { let start = Date.now(); let res = await value.apply(this, args); let millis = Date.now()-start; // 上报代码 return res; } return desc; } class Test{ @report getData(url){ // fetch 代码 } } let t = new Test() t.getData()
这样使用@report
修饰后的代码就会上报请求所消耗的时间。扩展或者修改report
函数不会影响业务代码,反之亦然。
咱们能够对原有代码进行简单的异常处理,而无需侵入式的修改:
function handleError(target, key, desc) { const { value } = desc; desc.value = async function (...args) { let res; try{ res = await value.apply(this, args); }catch(err){ // 异常处理 logger.error(err) } return res; } return desc; } class Test{ @handleError getData(url){ // fetch 代码 } } let t = new Test() t.getData()
经过以上两个示例咱们能够看到,修饰器的定义很简单,功能却很是强大。
咱们一步一步经过高阶函数、原型链、Object.defineProperty
和@Decorator
分别实现了装饰者模式。接下来在回顾一下:
有些朋友可能会以为装饰者模式和 vue 的 mixin 机制很像,其实他们都是“开放-封闭原则”和“单一职责原则”的体现。
附上代码 jsbin 地址: