[译]探秘ES2016中的Decorators

迭代器(iterators)、生成器(generators)、数组推导式(array comprehensions); 随着JavaScript与Python之间的类似度与日俱增,对于这种变化,我绝对是最high的那个。那今天咱们就来聊聊又一个Python化在ES2016(也叫ES7)里的体现——装饰器(Decorators),by Yehuda Katzjavascript

装饰器模式

装饰器(Decorator)究竟是个什么鬼?来让咱们从头儿说,在Python里,装饰器(Decorator)是一种极简的调用高阶函数(higher-order function)的语法。Python里,装饰器(Decorator)就是一个函数,她接受另外一个函数做为参数,而后在不直接修改这个函数的状况下,扩展该函数的行为,最终再将该函数返回。Python里一个最简单的装饰器(Decorator)使用起来长这个样子:html

@mydecorator
def myfunc():
    pass

最上面那个(@mydecorator)就是一个装饰器(Decorator),并且和ES2016(ES7)里的装饰器(Decorator)在特征上没什么区别,因此这里就要注意喽!java

@符号告诉解析器咱们正在使用一个名叫mydecorator的装饰器(Decorator),而且mydecorator就是真实的这个装饰器(Decorator)的定义函数的名字。装饰器(Decorator)要作的就是接受一个参数(那个即将被"装饰"的函数),封装额外功能,而后返回原被装饰的函数。python

当你不想修改一个函数,又想经过该函数的输入、输出作点儿额外工做的时候,装饰器(Decorator)就显得格外耀眼了。这类功能常见于:缓存、访问控制、鉴权、监控、计/定时器、日志、级别控制等等。react

ES五、ES2015(ES6)里的装饰器(Decorator)

ES5里,想要经过纯粹的函数实现一个装饰器(Decorator),很麻烦!从ES2015(ES6)开始,类原生支持继承,咱们就须要一种更好的方式在多个类之间共享同一段代码功能。一种更好的分配法。git

Yehuda在他的的提议里认为经过装饰器(Decorator)在设计阶段使用注解(annotating)、修改JavaScript类、属性和字面量对象等方式来保持代码的优雅。有兴趣的朋友看这里:Yehuda's Decorator Proposales6

下面仍是让咱们一块儿用ES2016(ES7)的装饰器(Decorator)来试试拳脚吧!github

上手ES2016的装饰器(Decorator)

先回顾一下咱们在Python里学到的东东。ES2016(ES7)里的装饰器(decorator)是这样一个表达式,她会返回一个函数,这个函数接受三个参数,分别是:targetnameproperty descriptor。咱们经过给这个表达式加前缀@,而且将这段表达式放在想要“装饰”的内容上面。装饰器(Decorator)能够经过类或者属性来定义。shell

装饰一个属性

下面是一个简单的Cat类:express

class Cat{
    meow(){
        return `${this.name} says Meow!`;
    }
}

prototype的方式定义这个类,在Cat.prototype上增长meow方法,大概是这个样子:

Object.defineProperty(Cat.prototype, 'meow', {
    value: specifiedFunction,
    enumerable: false,
    configurable: true,
    writable: true
});

咱们想要一个属性或者方法不可被赋值运算符改变。放一个装饰器(Decorator)在这个属性上面就行。咱们来写一个@readonly装饰器(Decorator):

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

而后,把这个装饰器(Decorator)加在meow属性上:

class Cat{
    @readonly
    meow(){
        return `${this.name} says Meow!`;
    }
}

装饰器(Decorator)是一个会被执行的表达式,她还必须再返回一个函数。意思是,@readonly或者@something(parameter)均可以是合法的装饰器(Decorator)。

那么,在property descriptor被注册到Cat.prototype以前,执行引擎会先执行装饰器(Decorator):

let descriptor = {
    value: specifiedFunction,
    enumerable: false,
    configurable: true,
    writable: true
};

//装饰器和`Object.defineProperty`具备相同的参数列表,
//有机会在真正的`defineProperty`之行以前作点儿事情
descriptor = readonly(Cat.prototype, 'meow', descriptor) || descriptor;
defineDecoratedProperty(Cat.prototype, 'meow', descriptor);

就这样,meow如今成了只读参数。来用如下代码验证咱们的实现:

var garfield = new Cat();

garfield.meow = function(){
    console.log('I want lasagne!');
};

//Exception: Attempted to assign to readonly property

碉堡了,有木有?待会儿咱们就来瞧瞧如何装饰一个类(只玩儿属性是否是有点儿low)。不过咱们仍是先来看一个类库(https://github.com/jayphelps/core-decora... by Jay Phelps)。尽管她还年轻,不过ES2016里的装饰器(Decorator)已经均可以经过她使用了哦。

和上面咱们写的给meow属性的@readonly装饰器同样,她已内置了@readonly实现,引入便可使用:

import { readonly } from 'core-decorators';

class Meal{
    @readonly
    entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';
//Cannot assign to read only property 'entree' of [object Object]

core-decorators里面还有其余经常使用的装饰器(Decorator)工具,譬如:@deprecate,当API须要一些变动提示信息时,能够写成这样:

调用console.warn()输出废弃提示信息。你也能传入一个自定义的信息覆盖默认值。甚至还能够给一个包含url的options对象,让用户能够了解更多

import { deprecate } from 'core-decorators';

class Person{
    @deprecate
    facepalm() {}

    @deprecate('We stopped facepalming')
    facepalmHard() {}

    @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
    facepalmHarder() {}
}

let captainPicard = new Person();

captainPicard.facepalm();
//DEPRECATION Person#facepalm: This function will be removed in future versions.

captainPicard.facepalmHard();
//DEPRECATION Person#facepalmHard: We stopped facepalming

captainPicard.facepalmHarder();
//DEPRECATION Person#facepalmHarder: We stopped facepalming
//
//     see: http://knowyourmeme.com/memes/facepalm for more details.
//

装饰一个类

下面咱们再来看看如何装饰一个类。在下面这个例子中,标准提议中称装饰器(Decorator)也能接受一个构造器。有一个小说类MySuperHero,咱们来为她写一个简单的@superhero装饰器:

function superhero(target){
    target.isSuperhero = true;
    target.power = 'flight';
}

@superhero
class MySuperHero{}

console.log(MySuperHero.isSuperhero);//true

咱们还能再进一步,但愿装饰器(Decorator)可以接受参数获取不一样的行为,就像工厂方法那样:

function superhero(isSuperhero) {
    return function(target){
        target.isSuperhero = isSuperhero;
    };
}

@superhero(true)
class MySuperHeroClass{}

console.log(MySuperHeroClass.isSuperhero);//true

@superhero(false)
class MySuperHeroClass{}

console.log(MySuperHeroClass.isSuperhero);//false

ES2016的装饰器(Decorator)在属性和类上皆可工做。装饰器能够自动获取传入的property nametarget object,咱们稍后会讲到。装饰器(Decorator)里能拿到descriptor使得譬如:用getter替换属性获取、自动绑定等之前要很繁琐才能完成的工做变为可能。

ES2016装饰器(Decorator)和Mixins

最近看Reg Braithwaite写ES2016 Decorators as mixins,以及以前的Functional mixins,很是赞同他的观点。Reg经过一个Helper向任意target(class prototype 或者对象)中植入不一样的行为,以此来表示一个具有指定行为的类。Functional mixin将不一样的实例行为植入类的prototype中,实现参考以下:

function mixin(behaviour, sharedBehaviour = {}) {
    const instanceKeys = Reflect.ownKeys(behaviour);
    const sharedKeys = Reflect.ownKeys(sharedBehaviour);
    const typeTag = Symbol('isa');

    function _mixin(target) {
        for (let property of instanceKeys) {
            Object.defineProperty(target.prototype, property, { value: behaviour[property] });
        }
        Object.defineProperty(target, typeTag, { value: true });
        return target;
    }
    for (let property of sharedKeys){
        Object.defineProperty(_mixin, property, {
            value: sharedBehaviour[property],
            enumerable: sharedBehaviour.propertyIsEnumerable(property)
        });
    }
    Object.defineProperty(_mixin, Symbol.hasInstance, {
        value: (i) => !!i[typeTag]
    });
    return _mixin;
}

屌!如今咱们能够定义mixins,而后用她们装饰其余类了。想象一下咱们有如下简单ComicBookCharacter类:

class ComicBookCharacter{
    constructor(first, last) {
        this.firstName = first;
        this.lastName = last;
    }

    realName() {
        return this.firstName + ' ' + this.lastName;
    }
}

这估计是史上最无聊的角色了,不过咱们能够定义一些mixins来给她提高一下能力,诸如SuperPowersUtilityBelt。来试试用Reg的mixins Helper吧:

const SuperPowers = mixin({
    addPower(name) {
        this.powers().push(name);
        return this;
    },
    powers() {
        return this._powers_pocessed || (this._powers_pocessed = []);
    }
});

const UtilityBelt = mixin({
    addToBelt(name) {
        this.utilities().push(name);
        return this;
    },

    utilities() {
        return this._utility_items || (this._utility_items = []);
    }
});

有了这些,咱们如今能够前缀@符号,把上述两个mixins做为装饰器(Decorator)挂在ComicBookCharacter上为其提供额外功能了。注意下面代码多装饰器(Decorators)是如何做用于类上的:

@SuperPowers
@UtilityBelt
class ComicBookCharacter {
    constructor(first, last) {
        this.firstName = first;
        this.lastName = last;
    }

    realName() {
        return this.firstName + ' ' + this.lastName;
    }
}

好吧,咱们如今要用上面定义好的类来创造一个蝙蝠侠喽:

const batman = new ComicBookCharacter('Bruce', 'Wayne');
console.log(batman.realName());
//Bruce Wayne

batman.addToBelt('batarang');
batman.addToBelt('cape');

console.log(batman.utilities());
//['batarang', 'cape']

batman.addPower('detective');
batman.addPower('voice sounds like Gollum has asthma');

console.log(batman.powers());
//['detective', 'voice sounds like Gollum has asthma']

经过装饰器(Decorators),代码更加简洁。我本身在使用过程当中将她们做为函数调用的另外一种选择,或者高阶函数组件的Helper。
注:@WebReflection也写了一个上述mixin的实现,你能够在这儿看看和Reg的版本有什么不一样。

经过Babel使用装饰器(Decorators)

装饰器(Decorator)现仍处于提议阶段,没被经过列入标准。咱们要感谢Babel提供了一个试验模式支持装饰器(Decorator)的转义实现,使得上述例子基本均可以直接使用。

若是使用Babel CLI,传入以下参数便可:

babel --optional es7.decorators

或者你能够本身写程序调用她的transformer:

babel.transform('code', {
    optional: ['es7.decorators']
});

Babel还有个在线版的REPL;选中“Experimental”复选框就能够激活装饰器(Decorator),来试试?

牛逼的各类实验

坐我旁边的是一个超走运的小伙儿,他叫Paul Lewis,用装饰器(Decorator)作了个试验性的功能重塑了读/写DOM的体验。其中借鉴了Wilson Page的FastDOM,不过提供了更简单的API接口。Paul的读/写装饰器(Decorator)甚至还能在@write执行时若是被装饰的函数内部有方法或者属性被调用来改变页面布局就会经过console警告你,同理当@read执行时若是有“写”类型的DOM变动也会收到警告。

下面是Paul试验代码是简单使用场景,若是在@read里试图变动DOM时,就会有异常信息打在console里:

class MyComponent {
    @read
    readSomeStuff() {
        console.log('read');

        //抛出异常
        document.querySelector('.button').style.top = '100px';
    }

    @write
    writeSomeStuff() {
        console.log('write');

        //抛出异常
        document.querySelector('.button').focus();
    }
}

开始用装饰器(Decorator)吧!

简短地说,ES2016的装饰器对声明式的装饰、注解、类型检查以及在ES2015里类上增长装饰器的各类奇淫技巧都很是有好处。往深里说,对静态分析(编译时的类型检查,自动补全)也是大有裨益。

她和经典的面向对象编程(OOP)里的装饰器也没有太大区别,OOP里是在对象上提供装饰器,或静态、或动态的植入不一样行为,但却不改变原类。
装饰器的语义其实在flux中也有体现,不管如何,咱们仍是最好时刻关注Yehuda的更新。

最近React社区也在讨论用装饰器替换以前的mixins设计来建立高阶组件了。

我我的看到这些试验性示例非常兴奋,但愿你也能用Babel试一把,说不定你也搞出来点好玩的东东,而后像Paul同样分享给你们了。

更多阅读

特别鸣谢:Jay Phelps, Sebastian McKenzie, Paul Lewis and Surma 的意见

原文地址:exploring-es7-decorators

相关文章
相关标签/搜索