ES6-30-装饰器

445 阅读3分钟

装饰器是一种与类相关的语法,可用来注释或修改类和类的方法
装饰器是一个函数,常以@+函数的形式出现。

@observer
class Foo {
    @enumberable(true)
    @throttle(500)
    method() {}
}

一、类的装饰

类的装饰器是一个对类进行处理函数,函数的参数就是待装饰的目标类。对类的处理相当于:

@F
class C{}

// 相当于
class C{}
C = F(C) || C
@testable
class MyTestableClass {
  // ...
}

// 目标类就是MyTestableClass
function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

1.多个参数

若一个参数不够,可以在装饰器函数外封装函数(偏函数),相当于改变了装饰器函数的行为。

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

对类进行装饰是在编译时完成,装饰器是在编译时 运行的函数

2.添加原型属性

类的原型属性的装饰使用target.prototype来操作

// mixins.js
export function mixins(...list) {
  return function (target) {
    Object.assign(target.prototype, ...list)
  }
}

// main.js
import { mixins } from './mixins'

const Foo = {
  foo() { console.log('foo') }
};

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // 'foo'

相当于

const Foo = {
  foo() { console.log('foo') }
};

class MyClass {}

Object.assign(MyClass.prototype, Foo);

let obj = new MyClass();
obj.foo() // 'foo'

上面其实实现了一个Mixin模式

3.案例(react)

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

可以写成:

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

二、方法的装饰

装饰器也可以用来装饰类的属性,此时的装饰器函数接收三个参数:

  • 类的原型对象(装饰器本意修饰实例,但此时尚未生成,故装饰类的原型
  • 待装饰的目标属性
  • 该属性的属性描述符
// 装饰类的属性
class Person {
  @nonenumerable
  get kidCount() { return this.children.length; }
}

function nonenumerable(target, name, descriptor) {
  descriptor.enumerable = false;
  return descriptor;
}
// 装饰类的属性方法
class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

function log(target, name, descriptor) {
  var oldValue = descriptor.value;

  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };

  return descriptor;
}

const math = new Math();

// passed parameters should get logged now
math.add(2, 4);

装饰类的属性就相当于:

Object.defineProperty(target.prototype, name, descriptor);

1.多个属性修饰器

多个属性修饰器会像洋葱模型,先从外到内进入,再从内向外执行(编译时执行

function dec(id){
  console.log('evaluated', id);
  return (target, property, descriptor) => console.log('executed', id);
  // 注意真正实现修饰的是这个return的函数
}

class Example {
    @dec(1)
    @dec(2)
    method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1
// 注意以上log均是在编译时出现

三、装饰器不适用于函数

装饰器不适用函数,是因为函数存在提升,类不存在提升:

var counter = 0;

var add = function () {
  counter++;
};

@add
function foo() {
    console.log(couter);
}

本意是想 foo执行时打印couter为1,实际为0。是因为:

@add
function foo() {
}

var counter;
var add;

counter = 0;

add = function () {
  counter++;
};

装饰函数可以使用高阶函数(偏函数)

function doSomething(name) {
  console.log('Hello, ' + name);
}

function loggingDecorator(wrapped) {
  return function() {
    console.log('Starting');
    const result = wrapped.apply(this, arguments);
    console.log('Finished');
    return result;
  }
}

const res = loggingDecorator(doSomething);

四、code-decrators.js

阅读第三方库理解装饰器

五、使用装饰器实现自动发布事件

const postal = require("postal/lib/postal.lodash");

export default function publish(topic, channel) {
  const channelName = channel || '/';
  const msgChannel = postal.channel(channelName);
  msgChannel.subscribe(topic, v => {
    console.log('频道: ', channelName);
    console.log('事件: ', topic);
    console.log('数据: ', v);
  });

  return function(target, name, descriptor) {
    const fn = descriptor.value;

    descriptor.value = function() {
      let value = fn.apply(this, arguments);
      msgChannel.publish(topic, value);
    };
  };
}
// index.js
import publish from './publish';

class FooComponent {
  @publish('foo.some.message', 'component')
  someMethod() {
    return { my: 'data' };
  }
  @publish('foo.some.other')
  anotherMethod() {
    // ...
  }
}

let foo = new FooComponent();

foo.someMethod();
foo.anotherMethod();
频道:  component
事件:  foo.some.message
数据:  { my: 'data' }

频道:  /
事件:  foo.some.other
数据:  undefined