介绍
上一篇中我们提到了装饰器模式,这也是一种非常实用的设计模式,主要的特点是非侵入式,能让组件功能能被更好的解耦和复用,解决了不同组件之间共享某些相同功能的难题。有时候我们并不关心函数的内部实现,仅仅想做功能增强,装饰器模式就能给我们带来良好的可读性的同时,减少我们修改需求的成本。
在 js 中,我们可以构造一个装饰器函数,接收任何一个组件函数,返回一个增强功能的新组件,原组件其他功能不受影响,并且当该增强功能不再使用时,只需要在调用处去除包裹的装饰器函数即可,这样可插拔式的功能体验可使得代码变得简单而优雅。
修饰函数的装饰器实现
正是因为装饰器模式带来的种种好处,在 es7 标准中已经正式引入了装饰器特性,标志符为 @。我们可以设定如下情况:设计一个通用型的日志补丁,能够输出组件中某些方法的调用日志的功能,我们可以这样写:
以下代码请务必在支持 es7 的环境下运行,如何使用请自行查阅 babel 相关文档
1 | class Component { |
不难看出,装饰器函数接收三个参数:被修饰对象,被修饰对象名,以及该对象属性描述符(参考Object.defineProperty中的descriptor属性),返回新的被修饰对象属性描述符。
修饰类的装饰器实现
装饰器不仅能被用于修饰类属性,甚至能够直接修饰类本身,增强类功能。这个特性也十分实用。本质上,装饰器的行为就是一个高阶函数,其作用全等于以下用法:
1 | function decorator(target) { |
之前在写 redux 相关的文章时也顺口提到过,connect 这个高阶组件也能够用装饰器模式来实现,使得导出类变得更美观且易读。
这是不使用装饰器的 connect 组件写法,高阶函数显得冗长不易读,export 导出物不够明显与直观。
1 | class Component extends React.Component {} |
使用装饰器后,写法变成了这样
1 | @connect(mapStateToProps, mapDispatchToProps) |
我们也参照上述写法,来实现自己的类装饰器。假设这样一种情况,我们需要对每一个类组件增添一个获取作者名的通用方法,那我们可以这样来做:
1 | @decorateAuthor |
需要注意的一点的是,在 js 中,装饰器只能用于类和类中的方法,不能用于函数。这是因为,装饰器函数是在编译时执行,而不是在运行时执行。因此,若之间在普通函数中使用会存在函数提升的问题,即先定义函数名称,并不赋值,在被修饰函数声明时只能取到 undefined 的装饰器值,导致调用结果与预期不符。
总而言之,在适当的时机使用装饰器模式能帮我们得到一份优雅、易读、可插拔式的代码体验,使用装饰器模式,不需要深入理解原有的代码逻辑就能直接开发新特性,在对老代码进行功能增强的时候,有着事半功倍的效果。