一、简介
装饰器依赖于
ES5
的Object.defineProperty
方法
1.1 Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象- 该方法允许精确添加或修改对象的属性。通过赋值来添加的普通属性会创建在属性枚举期间显示的属性(
for...in
或Object.keys
方法), 这些值可以被改变,也可以被删除。这种方法允许这些额外的细节从默认值改变。默认情况下,使用Object.defineProperty()
添加的属性值是不可变的
Object.defineProperty(obj, prop, descriptor) |
obj
:要在其上定义属性的对象。prop
:要定义或修改的属性的名称。descriptor
:将被定义或修改的属性描述符。- 返回值:被传递给函数的对象。
在
ES6
中,由于Symbol
类型 的特殊性,用Symbol
类型 的值来做对象的key
与常规的定义或修改不同,而Object.defineProperty
是定义key
为Symbol
的属性的方法之一
descriptor属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符
- 数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。
- 存取描述符是由
getter-setter
函数对描述的属性。
configurable
当且仅当该属性的
configurable
为true
时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为false
enumerable
enumerable
定义了对象的属性是否可以在for...in
循环和Object.keys()
中被枚举。- 当且仅当该属性的
enumerable
为true
时,该属性才能够出现在对象的枚举属性中。默认为false
。
二、Babel
安装编译
npm install --save-dev @babel/core @babel/cli |
新建
.babelrc
文件
{ |
再编译指定的文件
babel decorator.js --out-file decorator-compiled.js |
三、用法
装饰器主要用于
- 装饰类
- 装饰方法或属性
3.1 类的装饰
@testable |
上面代码中,
@testable
就是一个装饰器。它修改了MyTestableClass
这 个类的行为,为它加上了静态属性isTestable
。testable
函数的参数target
是MyTestableClass
类本身
基本上,装饰器的行为就是下面这样
@decorator |
也就是说,装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类
如果觉得一个参数不够用,可以在装饰器外面再封装一层函数
function testable(isTestable) { |
上面代码中,装饰器
testable
可以接受参数,这就等于可以修改装饰器的行为
注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
前面的例子是为类添加一个静态属性,如果想添加实例属性,可以通过目标类的
prototype
对象操作
// mixins.js |
上面代码通过装饰器
mixins
,把Foo
对象的方法添加到了MyClass
的实例上面
3.2 方法的装饰
装饰器不仅可以装饰类,还可以装饰类的属性
class Person { |
装饰器函数
readonly
一共可以接受三个参数。
function readonly(target, name, descriptor){ |
- 第一个参数是类的原型对象,上例是
Person.prototype
,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target
参数指的是类本身) - 第二个参数是 所要装饰的属性名
- 第三个参数是 该属性的描述对象
3.3 函数方法的装饰
- 装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升
- 另一方面,如果一定要装饰函数,可以采用高阶函数的形式直接执行
function doSomething(name) { |
四、使用场景
4.1 装饰器有注释的作用
@testable |
从上面代码中,我们一眼就能看出,
Person
类是可测试的,而name
方法是只读和不可枚举的
4.2 React 的 connect
实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样
class MyReactComponent extends React.Component {} |
有了装饰器,就可以改写上面的代码。装饰
@connect(mapStateToProps, mapDispatchToProps) |
4.3 loading
在
React
项目中,我们可能需要在向后台请求数据时,页面出现loading
动画。这个时候,你就可以使用装饰器,优雅地实现功能。
@autobind |
loadingWrap 函数如下:
export function loadingWrap(needHide) { |
4.4 log
为一个方法添加 log 函数,检查输入的参数:
class Math { |
4.5 autobind
class Person { |
我们很容易想到的一个场景是 React 绑定事件的时候
class Toggle extends React.Component { |
我们来写这样一个
autobind
函数:
const { defineProperty, getPrototypeOf} = Object; |
4.6 debounce
有的时候,我们需要对执行的方法进行防抖处理:
class Toggle extends React.Component { |
function _debounce(func, wait, immediate) { |
4.7 time
用于统计方法执行的时间:
function time(prefix) { |
4.8 mixin
用于将对象的方法混入
Class
中
const SingerMixin = { |
mixin
的一个简单实现如下
function mixin(...mixins) { |
Gitalking ...