Decorator 简介及实战

前言

用过 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 中提到的双向绑定,即是通过重写setget方法来实现的。所以在我们还未正式用上 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 等等工具类。所以,以后的工作中可以多多尝试。

参考


最后修改于 2017-11-05