decorator 学习小结

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 带来的简洁吧!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏linux驱动个人学习

Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析【转】

Android系统的运行时库层代码是用C++来编写的,用C++ 来写代码最容易出错的地方就是指针了,一旦使用不当,轻则造成内存泄漏,重则造成系统崩溃。不过系统为...

672
来自专栏Deep learning进阶路

C++随记(二)---动态分配内存问题(1)

C++随记(二)---动态分配内存问题(1) 面向对象的编程的一个特点就是在运行阶段(而不是编译阶段)进行决策。运行阶段决策提供了灵活性,可以根据当时的情况进行...

1790
来自专栏Play & Scala 技术分享

挑逗 Java 程序员的那些 Scala 绝技

有个问题一直困扰着 Scala 社区,为什么一些 Java 开发者将 Scala 捧到了天上,认为它是来自上帝之吻的完美语言;而另外一些 Java 开发者却对它...

1466
来自专栏栗霖积跬步之旅

1.4 isAlive()方法

方法isAlive()是判断当前线程是否处于活动状态。 线程代码: public class TestThread extends Thread{ @O...

1830
来自专栏Java爬坑系列

【Java入门提高篇】Day1 抽象类

  基础部分内容差不多讲解完了,今天开始进入Java提高篇部分,这部分内容会比之前的内容复杂很多,希望大家做好心理准备,看不懂的部分可以多看两遍,仍不理解的部分...

1816
来自专栏Java技术栈

跟我学 Java 8 新特性之 Stream 流(七)流与迭代器,流系列大结局!

恭喜你们,马上就要学完Java8 Stream流的一整系列了,其实我相信Stream流对很多使用Java的同学来说,都是一个知识盲点,因为这个原因,我才这么细致...

421
来自专栏吴伟祥

编码总结笔记 原

我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一...

1226
来自专栏撸码那些事

编码最佳实践——里氏替换原则

Liskov替换原则(Liskov Substitution Principle)是一组用于创建继承层次结构的指导原则。按照Liskov替换原则创建的继承层次结...

952
来自专栏owent

理解Protobuf的数据编码规则

之前用Google的Protobuf感觉真是个很好用的东西,于是抽时间研究了下他的数据的存储方式,以后可以扩展其他语言的解析器。其实与其说是研究,不如说是翻译。...

841
来自专栏轮子工厂

卧槽,为什么你的程序执行到一半就退出了,原来是因为加了这个

快到月底了,相信有很多人都和呆博一样,不是“快揭不开锅了”,而是已经快要把锅都吃了〒▽〒。没关系我们可以一起吃掉这篇精神食粮啊,营养又健康,如果觉得味道还不错,...

1502

扫码关注云+社区