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