黎清龙

2017-01-26 22:10

decorator 学习小结

本文作者:IMWeb 黎清龙 原文出处:IMWeb社区 未经同意,禁止转载

decorator 学习小结

1. decorator 是什么

decorator 是装饰者,是 ES7 的语法。

decorator 本质是一个 wrapper,可以动态增强【类】,【实例方法】的能力

被装饰者对于装饰者是毫无感知的

读者如果感兴趣,可以深入学习【装饰者模式】

要注意的是,在 ES7 中,decorator 只是一个语法糖而已,和 class 这些一样

可以参考 babel 转换之后的 ES5 代码

2. decorator 的用法

2.1. 作用在类方法上

decorator 可以在定义类的时候,作用在类方法上,见下面的例子:

function readonly(target, key, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

class People {
    @readonly
    sayName() {
        console.log(this.name);
    }
}

var p = new People();
p.sayName = 'hello';

执行上面的代码,会报错,如下:

decorator 本质是一个 wrapper 函数,当 decorator 作用在类方法的时候

它的签名是function(target, key, descriptor)

  • target: 类原型,也就是 People.prototype
  • key: 属性名,也就是sayName
  • descriptor: 属性的描述符

可以发现,decorator 的签名和 Object.defineProperty 的签名是一样的

本质上来说,作用在类方法的 decorator 就是对 Object.defineProperty 的 wrapper

2.2. 作用在类上

decorator 可以在定义类的时候,作用在类上面,对类进行增强

function nonTestable(target) {
    target.canTest = false;
}

@nonTestable
class People {}

console.log(People.canTest); // false

当 decorator 作用在类上时,只会传入一个参数,就是类本身

2.3. decorator 也可以是工厂函数

当同一个 decorator 在作用在不同目标上面时,有不同的表现时,可以把 decorator 定义为一个工厂函数,详见下面的例子:

function addName(name) {
    return function(target) {
        target.myName = name;
    };
}

@addName('people')
class People {
    static say() {
        console.log(People.myName);
    }
}

@addName('dog')
class Dog {
    static say() {
        console.log(Dog.myName);
    }
}

People.say(); // people
Dog.say(); // dog

3. 如何使用 decorator?

既然是 ES7 的新语法,那么如何使用 decorator?

目前需要使用 babel 来进行转换才能使用,具体方法如下:

.babelrc文件如下:

{
  "presets": ["es2015", "stage-1"],
  "plugins": [
    "babel-plugin-transform-decorators-legacy"
  ]
}

然后需要安装插件:

npm i babel-cli babel-preset-es2015 babel-preset-stage-1 babel-plugin-transform-decorators

安装完成之后,就可以使用 babel 来转换带 decorator 的代码了:

babel index.js > index.es5.js

4. 应用 decorator

实际上,decorator 有非常广泛的运用场景,这里我简单举两个例子

4.1. 利用 decorator 实现 mixin

废话少说,直接举例子:

function nameMixin(target) {
    target.prototype.setName = function(name) {
        this.name = name;
        return this;
    };

    target.prototype.sayName = function() {
        console.log(this.name);
    };
}

function ageMixin(target) {
    target.prototype.setAge = function(age) {
        this.age = age;
        return this;
    };

    target.prototype.sayAge = function() {
        console.log(this.age);
    };
}

@nameMixin
@ageMixin
class People {}

var p = new People();
p.setName('peter').sayName(); // peter
p.setAge(18).sayAge(); // 18

可以对比一下以前的 mixin 使用方式:

class People extends mixin(ageMixin, nameMixin) {}
// or
class People {}
mixin(People, ageMixin, nameMixin);

可以看到,使用 decorator 之后,代码简单明了了许多

4.2. 利用 decorator 实现 AOP

function aop(before, after) {
    return function(target, key, descriptor) {
        const method = descriptor.value;
        descriptor.value = (...args) => {
            let ret;

            before && before(...args);
            ret = method.apply(target, args);
            if (after) {
                ret = after(ret);
            }

            return ret;
        };
    };
}

function beforeTest1(opt) {
    opt.name = opt.name + ', haha';
}

function beforeTest2(...args) {}

function afterTest2(ret) {
    console.log('haha, add 10!');
    return ret + 10;
}

class Test {
    @aop(beforeTest1)
    static test1(opt) {
        console.log(`hello, ${opt.name}`);
    }

    @aop(beforeTest2, afterTest2)
    static test2(...args) {
        return args.reduce((a, b) => a + b);
    }
}

/**
 * out:
 * 
 * hello, peter, haha
 * haha, add 10!
 * 16
 * 
 */
Test.test1({
    name: 'peter'
});
console.log(Test.test2(1, 2, 3));

这里只是实现了一个简单的 aop 例子

大家可以看到,使用了 decorator 之后,代码非常清晰

5. 小结

decorator 是 ES7 的新语法,本质上来说,它就是一个语法糖,但是缺非常有用

任何装饰者模式的代码都可以通过 decorator 来实现

使用了 decorator 之后,代码会变得清晰明了

相信不久的将来,ES7 语法将会流行,但是在这之前,你仍然可以通过 babel 来使用 decorator

快来享受 decorator 带来的简洁吧!

5条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。