类的修饰器
修饰器对类的行为的改变是在代码编译时候发生的,而不是在运行的时候1
2
3
4
5
6
7
8function testable(target){
target.isTestable = true
}
@testable
class MyTestableClass{}
console.log(MyTestableClass.isTestable)
修饰器的行为如下1
2
3
4
5
6@decorator
class A{}
//等同于
class A{}
A = decorator(A) || A;
如果需要多个参数,就在修饰器的外部在封装一层函数1
2
3
4
5
6
7
8
9function testable(isTestable){
return function (target){
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable; //true
以上是为类添加静态属性,下面为类的实例添加属性,通过目标类的prototype
对象操作1
2
3
4
5
6
7
8
9function testable(target){
target.prototype.isTestable = true;
}
@testable
class MyTestableClass {}
let obj = new MyTestableClass();
obj.isTestable;
使用mixin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//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'
方法的修饰
1 | class Person{ |
此时,修饰器接受三个参数:
1) 所要修饰的目标对象
2) 所要修饰的属性名
3) 该属性的描述对象
1 | function readonly(target,name,descriptor){ |
下面的@log
修饰器,可以起到输出日志的效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37class Math{
@log
add(a,b){
return a+b;
}
}
function log(target,name,descriptor){
var oldVal = descriptor.value; //先保存函数的引用
descriptor.value = function(){
console.log(`Calling "${name}" with`,arguments);
return oldVal.apply(null,arguments)
}
return descriptor;
}
const math = new Math();
math.add(1,2);
//----------------------------------
var obj = {
p(a,b){
return a+b
}
};
console.log(Object.getOwnPropertyDescriptor(obj, 'p'))
/*
Object {
"configurable": true,
"enumerable": true,
"value": [Function p],
"writable": true
}
*/
如果一个方法有多个修饰器,先从外到内进入,再由内到外执行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function desc(id){
console.log('evaluated',id);
return (target,property,descriptor) => conosle.log('executed',id);
}
class Example {
@desc(1)
@desc(2)
method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1
修饰器不能用于函数
因为存在函数提升1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var counter = 0;
var add = function(){
counter++
}
@add
function foo(){}
//由于存在变量提升,所以实际的执行如下
@add
function foo(){}
var counter;
var add;
counter =0;
add = function(){
counter++;
}
Mixin
是对象继承的一种替代,在一个对象之中混入另一个对象的方法1
2
3
4
5
6
7
8
9
10
11const Foo = {
foo(){
console.log('foo')
}
}
class MyClass{}
Object.assign(MyClass.prototype,Foo)
let obj = new MyClass();
obj.foo();
将mixin写成修饰器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//mixins.js
export function mixins(...list){
return function(target){
Object.assign(target.prototype,...list)
}
}
import {mixins} from './mixins';
const Foo = {
foo(){
console.log('foo')
}
}
@mixins(Foo)
class MyClass{}
let obj = new MyClass();
obj.foo(); //'foo'
以上方法会改写MyClass
的prototype
对象,所以可以使用类的继承实现mixin
1
2
3class MyClass extends MyBaseClass {
//...
}
如果我们想在MyClass
中混入foo
方法,可以在MyClass
和MyBaseClass
之间插入一个混合类1
2
3
4
5let MyMixin = (superclass)=> class extends superclass {
foo(){
console.log('foo from Mymixin');
}
}
上面接受superclass
为参数,返回一个继承superclass
的子类,该子类包含一个foo
方法1
2
3
4
5
6class MyClass extends MyMixin(MyBaseClass) {
//...
}
let c = new MyClass();
c.foo();
上面的写法有一个好处就是可以调用super
,从而避免在混入过程中覆盖父类的同名函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33let Mixin1 = (superclass) => class extends superclass{
foo(){
console.log('foo from Mixin1');
if(super.foo) super.foo();
}
}
let Mixin2 = (superclass) => class extends superclass{
foo(){
console.log('foo from Mixin2');
if(super.foo) super.foo();
}
}
class S {
foo() {
console.log('foo from S');
}
}
class C extends Mixin1(Mixin2(S)) {
foo() {
console.log('foo from C');
super.foo();
}
}
let c = new C();
c.foo();
//"foo from C"
//"foo from Mixin1"
//"foo from Mixin2"
//"foo from S"
使用修饰器实现自动发布事件
利用修饰器,使得对象的方法被调用的时候,自动发出一个事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import postal from 'postal/lib/postal.lodash';
export default function publish(topic,channel){
return function(target,name,descriptor){
const fn = descriptor.value;
descriptor.value = function(){
let value = fn.apply(this,arguments);
postal.channel(channel || target.channel || "/").publish(topic, value);
}
}
};
//使用
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() // 在"component"频道发布"foo.some.message"事件,附带的数据是{ my: "data" }
foo.anotherMethod() // 在"/"频道发布"foo.some.other"事件,不附带数据