美文网首页前端笔记
Decorator 修饰器

Decorator 修饰器

作者: faremax | 来源:发表于2017-10-03 21:15 被阅读12次

    修饰器

    修饰器是 ES7 提出的一个提案,用来修改类的行为。目前需要 babel 才可以使用。它最大的特点是:可以在编译期运行代码!其本质也就是在编译器执行的函数。其执行格式如下:

    @decorator    //decorator 是修饰器名,即函数名
    class A{}
    //相当于
    class A{}
    A = decorator(A) || A;
    

    修饰器函数接受3个参数,依次是目标函数、属性名(可忽略)、该属性的描述对象(可忽略)。

    function test(target){
      target.isTestable = true;               //利用修饰器给类添加静态属性
      target.prototype.isTestable = true;     //利用修饰器给类添加动态属性
    }
    
    @test
    class A{}
    
    console.log(A.isTestable);       //true
    console.log(new A().isTestable);   //true
    

    例如之前的 mixin 可以用修饰器实现一个简单的版本:

    function mixins(...list){
      return function(target){
        Object.assign(target.prototype, ...list);
      }
    }
    var Foo = {
      foo(){console.log("foo");}
    };
    @mixins(Foo)
    class Cla{}
    let obj = new Cla();
    obj.foo();     //"foo"
    

    修饰器不仅仅可以修饰类,还可以修饰类的属性和方法:

    function readonly(target, name, descriptor){
      descriptor.writable = false;
      return descriptor;
    }
    
    class Person{
      constructor(name, age, tel){
        this.name = name;
        this.id = id;
      }
      @readonly
      id(){return this.id};
    }
    

    当然也可以同时调用2个修饰器:

    function readonly(target, name, descriptor){
      descriptor.writable = false;
      return descriptor;
    }
    function nonenumerable(target, name, descriptor){
      descriptor.enumerable = false;
      return descriptor;
    }
    
    class Person{
      constructor(name, age, tel){
        this.name = name;
        this.id = id;
      }
      @readonly
      @nonenumerable
      id(){return this.id};
    }
    

    使用修饰器应该注意:虽然类本质是个函数,但修饰器不能用于函数,因为函数具有声明提升。

    core-decroators.js

    这是个三方模块,使用import {function Namelist} from 'core-decroators';引入。它提供了几个常见的修饰器:

    • @autobind
      是对象中的 this 始终绑定原始对象:
    class Person{
      @autobind
      whoami(){
        return this;
      }
    }
    let person = new Person();
    let getPerson = person.getPerson;
    
    getPerson() === person;    //true
    
    • @readonly
      使得属性方法只读
    class Person{
      @readonly
      id = gen();     //gen 是一个计数器
    }
    var p = new Person()
    p.id = 123;   //Cannot assign to read only property 'id' of [object Object]
    
    • @override
      检查子类方法是否正确的覆盖了父类的同名方法,如果不正确会报错
    class Person{
      work(){console.log("I am working");}
    }
    class Coder extends Person{
      @override
      work(){console.log("I am coding");}   //如果不正确会在这里报错
    }
    
    • @deprecate(也作: @deprecated)
      在控制台显示一条 warning,表示该方法不久后将被废除,接受一个可选的参数作为警告内容, 接受第二个参数(对象)表示更多信息
    class Person{
      @deprecate
      facepalm(){}
    
      @deprecate('We stopped facepalming')
      facepalmHard(){}
    
      @deprecate('We stopped facepalming', {url:'http://balabala.com'})
      facepalmHarder(){}
    }
    
    • @suppressWarnings
      抑制 deprecate 修饰器导致调用 console.warn(), 但异步代码发出的除外。
    class Person{
      @deprecate
      facepalm(){}
    
      @supressWarnings
      facepalmWithoutWarning(){
        this.facepalm();
      }
    }
    let p = new Person();
    p.facepalm();    //控制台显示警告
    p.facepalmWithoutWarning();    //没有警告
    

    其它第三方修饰器

    此外还有一些库提供一些其他功能,比如 Postal.js(Github)中的 @publish, 可以在函数调用时发布一个事件:

    import publish from "../to/decorators/publish";
    
    class FooComponent{
      @publish("foo.some.message", "component")
      someMethod(){}
    
      @publish("foo.some.other", "")
      anotherMethod(){}
    }
    

    再比如 Trait(Github), 和 mixin 功能类似,提供了更强大的功能:防止同名冲突,排除混入某些方法,为混入方法起别名等

    import {traits} from 'traits-decorator'
    
    class TFoo{
      foo(){console.log("foo1")}
    }
    class TBar{
      bar(){console.log("bar")}
      foo(){console.log("foo2")}
    }
    
    @traits(TFoo, TBar)       //会报错,因为这两个类中有同名方法
    class MyClass{}
    
    let obj = new MyClass();
    //如果没有第八行的同名方法,输出如下
    obj.foo();   //"foo1"
    obj.bar();   //"bar"
    

    但是我们可以修改上面第11行排除这个 foo,让它可以被覆盖:

    @traits(TFoo, TBar::excludes('foo'))
    class MyClass{}
    

    也可重命名同名方法:

    @traits(TFoo, TBar::alias(foo:'aliasFoo'))
    class MyClass{}
    

    当然绑定运算符可以链式调用:

    //假设还有个同名的 baz 方法
    @traits(TFoo, TBar::excludes('foo')::alias(baz:'aliasBaz'))
    class MyClass{}
    
    //另一种写法
    @traits(TFoo, TBar::as({excludes: ['foo'], alias: {baz:'aliasBaz'}}))
    class MyClass{}
    

    相关文章

      网友评论

        本文标题:Decorator 修饰器

        本文链接:https://www.haomeiwen.com/subject/yxsfyxtx.html