前言
用过 Java 的朋友都知道,装饰器(Decorator)是一种十分非常方便改变类运行是行为的一种方式,例如在 Spring 框架中我们用到比较多的注解(@Autowired), 通过它可以自动实例化对象,从而可以精简代码。装饰器是典型的 AOP(Aspect-Oriented-Programming) 编程的应用,类似的 CSS 对于 HTML 的样式也是类似于这种思想,即从切面来改变和影响主对象的行为。
Decorator 简介
Decorator 是一种通过注解表达式就可以扩展类或者方法的函数。Decorator 可以应用到任何一个 class 或者 property 上。列如:
@myDecorator class A {} // 作用class
@myDecorator
doSomething() {} // 作用表达式
Javascript Decorator 目前任然是 ES7 提案状态,更多该特性的进度可以访问proposal-decorators查看。
Decorator 原理
说到更改对象的属性或者方法,大家肯定会想到Object.defineProperty(obj, prop, descriptor)
方法,通过该方法,我们可以轻易的修改或者重写对象的行为或者属性,之前 Vue 中提到的双向绑定,即是通过重写set
和get
方法来实现的。所以在我们还未正式用上 Decorator 前,都是通过 Object.defineProperty 方法来实现。先来简单的认识下这个方法:
/**
* obj : 需要修改属性的对象
* prop : 需要修改对象的属性名称
* descriptor: 用来定义属性具体行为的描述对象
**/
Object.defineProperty(obj, prop, descriptor)
descriptor 属性说明
configurable : 定义属性对象是否可以被配置,即如果为 false ,定义修改的描述操作(writeable, get 等等)都无效
enumerable : 是否可以通过 for-in 来遍历,或者 Object.keys 列举
value : 定义对象 value 属性的值,value 可以是 number, object, function 等等
writable: 定义 value 值是否可以被重写
get: 一个访问 value 属性时会触发的 function 对象
set: 一个设置 value 属性时会触发的 function 对象
修改一个属性为只读(readonly)
了解 Object.defineProperty 的基本语法后,我通过它先简单实现一个 readonly 实例。具体代码如下:
Decorator 的基本语法与使用
# 定义
function myDecoration(target, name, descriptor) {}
# 对property使用
class A {
@myDecorator
test() {}
}
# 对class使用
@myDecorator
class A {}
# 带参数
function myDescorator(a) {
return function (target, name, descriptor) {
console.llog('params:', a)
}
}
@myDescorator(a)
class A {}
# 时使用多个装饰器(Decorator)
@myDecorator1
@myDecorator2
class A {}
利用 Decorator 语法糖修改一个属性为只读(readonly)
利用 Decorator 给 React 组件封装 PureRender
我们都知道,在 React 生命周期里有一个shouldComponentUpdate
方法,该方法通过返回ture
或者 false
来确定组件是否重新 render 组件。也就是说,通过该方法我们可以过滤掉些无效的数据渲染事件,从而提升性能。例如我们针对 props 传递过来的数据对象进行对比,如果 props 对象的属性以及值并未变更的情况下,则无需执行 render 方法。
显然通过对比 props 下数据对象的属性与值是否变更,这种逻辑是可以复用的,而不是在单独的在每个组件中去在重复的写shouldComponentUpdate
方法。说到改变组件对象的方法行为,这里我们显然就可以使用
Decorator 来这个特性来做了,即我们对应用 Decorator 对象的 shouldComponentUpdate
进行重写。通过遍历 props 对象的属性和值,并与老 props 的属性与值进行对比,从而确定是否需要重新渲染。具体代码如下:
function isEqual(a, b) {
for (const key in a) {
if ({}.hasOwnProperty.call(a, key) &&
(!{}.hasOwnProperty.call(b, key) || a[key] !== b[key])) {
return false;
}
}
for (const key in b) {
if ({}.hasOwnProperty.call(b, key) && !{}.hasOwnProperty.call(a, key)) {
return false;
}
}
return true;
}
export default function pureRender(targetComponent) {
targetComponent.prototype.shouldComponentUpdate = function (props, state) {
return !isEqual(this.state, state) || !isEqual(this.props, props)
}
}
// 使用
@pureRender
class ComponentA extends React.Component {}
通过 Babel 使用 Decorator
由于 Decorator 是 ES7 中的草案,所以现在需要通过 Bable 才能使用。使用方法如下:
安装
npm install –save-dev babel-plugin-transform-decorators
使用
方法一、 通过配置.babelrc
{
"plugins": ["transform-decorators"]
}
方法二、通过 CLI
babel –plugins transform-decorators script.js
方法三、通过 Node API
require("babel-core").transform("code", {
plugins: ["transform-decorators"]
});
总结
通过 Decorator 这种不需要直接在对象或者方法中编写额外逻辑的方式,就可以轻易的扩展对象或者方法的能力,既满足了功能需求,也精简了代码,保证了代码的可维护性,例如我们已经常见的@log, @test, @mixin 等等工具类。所以,以后的工作中可以多多尝试。
参考
- 细说 ES7 JavaScript Decorators
- Decorator specification
- Exploring EcmaScript Decorators
- Object.defineProperty
- Babel Legacy Decorator plugin
- core-decorators
最后修改于 2017-11-05