修饰器

日期:2019-10-15编辑作者:www.6165.com

修饰器

  1. 类的梳洗
  2. 方法的修饰
  3. 干什么修饰器无法用来函数?
  4. core-decorators.js
  5. 选拔修饰器达成全自动公布事件
  6. Mixin
  7. Trait
  8. Babel转码器的支撑

类的梳洗

修饰器(Decorator)是贰个函数,用来修改类的作为。这是ES7的三个提案,方今Babel转码器已经支撑。

修饰器。修饰器对类的一举一动的更动,是代码编写翻译时产生的,并非在运作时。那表示,修饰器能在编写翻译阶段运转代码。

function testable(target) {
  target.isTestable = true;
}

@testable
class MyTestableClass {}

console.log(MyTestableClass.isTestable) // true

地点代码中,@testable正是叁个修饰器。它修改了MyTestableClass其一类的表现,为它足够了静态属性isTestable

基本上,修饰器的一举一动便是底下那样。

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

也正是说,修饰器本质正是编写翻译时施行的函数。

修饰器函数的率先个参数,正是所要修饰的靶子类。

function testable(target) {
  // ...
}

地点代码中,testable函数的参数target,正是会被修饰的类。

如若感到叁个参数缺乏用,可以在修饰器外面再封装一层函数。

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

地方代码中,修饰器testable能够承受参数,那就约等于能够修改修饰器的表现。

日前的例证是为类加多贰个静态属性,假如想增添实例属性,能够经过目的类的prototype对象操作。

function testable(target) {
  target.prototype.isTestable = true;
}

@testable
class MyTestableClass {}

let obj = new MyTestableClass();
obj.isTestable // true

地点代码中,修饰器函数testable是在指标类的prototype目的上加多属性,因而就可以在实例上调用。

上边是其他三个例子。

// 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'

上面代码通过修饰器mixins,把Foo类的不二等秘书技增多到了MyClass的实例下边。能够用Object.assign()模仿那么些效果。

const Foo = {
  foo() { console.log('foo') }
};

class MyClass {}

Object.assign(MyClass.prototype, Foo);

let obj = new MyClass();
obj.foo() // 'foo'

方法的修饰

修饰器既能够修饰类,还足以修饰类的习性。

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

地方代码中,修饰器readonly用来修饰“类”的name方法。

那会儿,修饰器函数一共能够承受几个参数,第二个参数是所要修饰的指标对象,第贰个参数是所要修饰的属性名,第三个参数是该属性的呈报对象。

function readonly(target, name, descriptor){
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}

readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);

上边代码表达,修饰器(readonly)会修改属性的汇报对象(descriptor),然后被改变的汇报对象再用来定义属性。

上边是另三个例证,修改属性描述对象的enumerable特性,使得该属性不可遍历。

class Person {
  @nonenumerable
  get kidCount() { return this.children.length; }
}

function nonenumerable(target, name, descriptor) {
  descriptor.enumerable = false;
  return descriptor;
}

修饰器。下面的@log修饰器。修饰器,能够起到输出日志的功效。

class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

function log(target, name, descriptor) {
  var oldValue = descriptor.value;

  descriptor.value = function() {
    console.log(`Calling "${name}" with`, arguments);
    return oldValue.apply(null, arguments);
  };

  return descriptor;
}

const math = new Math();

// passed parameters should get logged now
math.add(2, 4);

下面代码中,@log修饰器。修饰器的功效正是在施行原始的操作此前,实行贰遍console.log,进而达到输出日志的指标。

修饰器有注释的效率。

@testable
class Person {
  @readonly
  @nonenumerable
  name() { return `${this.first} ${this.last}` }
}

从地方代码中,我们一眼就能够观望,Person类是可测量检验的,而name办法是只读和成千成万的。

一旦同八个办法有几个修饰器,会像剥玉葱同样,先从外到内步向,然后由内向外执行。

function dec(id){
    console.log('evaluated', id);
    return (target, property, descriptor) => console.log('executed', id);
}

class Example {
    @dec(1)
    @dec(2)
    method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1

地点代码中,外层修饰器@dec(1)先进入,不过内层修饰器@dec(2)先执行。

除了注释,修饰器仍是能够用来项目检查。所以,对于类来讲,那项成效非凡平价。从长久来看,它将是JavaScript代码静态剖析的显要工具。

缘何修饰器不可能用于函数?

修饰器只好用于类和类的情势,不能用于函数,因为存在函数升高。

var counter = 0;

var add = function () {
  counter++;
};

@add
function foo() {
}

地点的代码,意图是实行后counter约等于1,可是其实结果是counter等于0。因为函数提高,使得实际执行的代码是下面那样。

@add
function foo() {
}

var counter;
var add;

counter = 0;

add = function () {
  counter++;
};

上面是另叁个事例。

var readOnly = require("some-decorator");

@readOnly
function foo() {
}

上边代码也是有题目,因为其实实行是下面那样。

var readOnly;

@readOnly
function foo() {
}

readOnly = require("some-decorator");

总的说来,由于存在函数升高,使得修饰器无法用来函数。类是不会晋级的,所以就一向不那方面包车型地铁主题素材。

core-decorators.js

core-decorators.js是二个第三方模块,提供了多少个常见的修饰器,通过它能够更加好地驾驭修饰器。

(1)@autobind

autobind修饰器使得方法中的this对象,绑定原始对象。

import { autobind } from 'core-decorators';

class Person {
  @autobind
  getPerson() {
    return this;
  }
}

let person = new Person();
let getPerson = person.getPerson;

getPerson() === person;
// true

(2)@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]

(3)@override

override修饰器检查子类的艺术,是不是科学覆盖了父类的同名方法,假使不得法会报错。

import { override } from 'core-decorators';

class Parent {
  speak(first, second) {}
}

class Child extends Parent {
  @override
  speak() {}
  // SyntaxError: Child#speak() does not properly override Parent#speak(first, second)
}

// or

class Child extends Parent {
  @override
  speaks() {}
  // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain.
  //
  //   Did you mean "speak"?
}

(4)@deprecate (别名@deprecated)

deprecatedeprecated修饰器在调控台显示一条警示,表示该措施将放任。

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 person = new Person();

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

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

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

(5)@suppressWarnings

suppressWarnings修饰器禁绝decorated修饰器导致的console.warn()调用。不过,异步代码发出的调用除了那么些之外。

import { suppressWarnings } from 'core-decorators';

class Person {
  @deprecated
  facepalm() {}

  @suppressWarnings
  facepalmWithoutWarning() {
    this.facepalm();
  }
}

let person = new Person();

person.facepalmWithoutWarning();
// no warning is logged

运用修饰器达成机关发布事件

咱俩得以选拔修饰器,使得对象的办法被调用时,自动发出贰个事变。

import 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);
    };
  };
}

地点代码定义了贰个名称叫publish的修饰器,它经过改写descriptor.value,使得原方法被调用时,会活动发出贰个风浪。它采用的风云“发布/订阅”库是Postal.js。

它的用法如下。

import publish from "path/to/decorators/publish";

class FooComponent {
  @publish("foo.some.message", "component")
  someMethod() {
    return {
      my: "data"
    };
  }
  @publish("foo.some.other")
  anotherMethod() {
    // ...
  }
}

此后,只要调用someMethod或者anotherMethod,就能活动发出二个风浪。

let foo = new FooComponent();

foo.someMethod() // 在"component"频道发布"foo.some.message"事件,附带的数据是{ my: "data" }
foo.anotherMethod() // 在"/"频道发布"foo.some.other"事件,不附带数据

Mixin

在修饰器的根基上,能够兑现Mixin模式。所谓Mixin格局,正是指标承袭的一种替代方案,中文译为“混入”(mix in),意为在八个指标之中混入此外叁个对象的秘技。

请看下边包车型大巴例证。

const Foo = {
  foo() { console.log('foo') }
};

class MyClass {}

Object.assign(MyClass.prototype, Foo);

let obj = new MyClass();
obj.foo() // 'foo'

地点代码之中,对象Foo有一个foo方法,通过Object.assign方法,可以将foo方法“混入”MyClass类,导致MyClass的实例obj目的都有所foo措施。那便是“混入”形式的多少个粗略完成。

上边,大家安插叁个通用脚本mixins.js,将mixin写成叁个修饰器。

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"

由此mixins那么些修饰器,达成了在MyClass类上边“混入”Foo对象的foo方法。

可是,上边的方法会改写MyClass类的prototype对象,假诺不希罕这或多或少,也足以通过类的接续达成mixin。

class MyClass extends MyBaseClass {
  /* ... */
}

地点代码中,MyClass继承了MyBaseClass。假诺大家想在MyClass里面“混入”一个foo主意,一个主意是在MyClassMyBaseClass时期插入一个混入类,那一个类具备foo艺术,况且三回九转了MyBaseClass的具有办法,然后MyClass再持续那么些类。

let MyMixin = (superclass) => class extends superclass {
  foo() {
    console.log('foo from MyMixin');
  }
};

上面代码中,MyMixin是贰个混入类生成器,接受superclass用作参数,然后回来一个三番五次superclass的子类,该子类富含二个foo方法。

随时,指标类再去继续那个混入类,就完成了“混入”foo方法的指标。

class MyClass extends MyMixin(MyBaseClass) {
  /* ... */
}

let c = new MyClass();
c.foo(); // "foo from MyMixin"

一经要求“混入”三个办法,就变化四个混入类。

class MyClass extends Mixin1(Mixin2(MyBaseClass)) {
  /* ... */
}

这种写法的叁个平价,是足以调用super,由此可以制止在“混入”进度中覆盖父类的同名方法。

let 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();
  }
}

地方代码中,每三遍混入发出时,都调用了父类的super.foo办法,导致父类的同名方法未有被覆盖,行为被保存了下去。

new C().foo()
// foo from C
// foo from Mixin1
// foo from Mixin2
// foo from S

Trait

Trait也是一种修饰器,效果与Mixin类似,可是提供更加多职能,举个例子防备同名方法的冲突、排除混入某个方法、为混入的点子起外号等等。

下边选取traits-decorator其一第三方模块作为例子。那几个模块提供的traits修饰器,不只可以够承受对象,仍是能够承受ES6类作为参数。

import { traits } from 'traits-decorator';

class TFoo {
  foo() { console.log('foo') }
}

const TBar = {
  bar() { console.log('bar') }
};

@traits(TFoo, TBar)
class MyClass { }

let obj = new MyClass();
obj.foo() // foo
obj.bar() // bar

下边代码中,通过traits修饰器,在MyClass类上面“混入”了TFoo类的foo方法和TBar对象的bar方法。

Trait不允许“混入”同名方法。

import { traits } from 'traits-decorator';

class TFoo {
  foo() { console.log('foo') }
}

const TBar = {
  bar() { console.log('bar') },
  foo() { console.log('foo') }
};

@traits(TFoo, TBar)
class MyClass { }
// 报错
// throw new Error('Method named: ' + methodName + ' is defined twice.');
//        ^
// Error: Method named: foo is defined twice.

上边代码中,TFoo和TBar都有foo方法,结果traits修饰器报错。

一种缓慢解决措施是驱除TBar的foo方法。

import { traits, excludes } from 'traits-decorator';

class TFoo {
  foo() { console.log('foo') }
}

const TBar = {
  bar() { console.log('bar') },
  foo() { console.log('foo') }
};

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

let obj = new MyClass();
obj.foo() // foo
obj.bar() // bar

下边代码应用绑定运算符(::)在TBar上海消防除foo方法,混入时就不会报错了。

另一种方法是为TBar的foo方法起八个小名。

import { traits, alias } from 'traits-decorator';

class TFoo {
  foo() { console.log('foo') }
}

const TBar = {
  bar() { console.log('bar') },
  foo() { console.log('foo') }
};

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

let obj = new MyClass();
obj.foo() // foo
obj.aliasFoo() // foo
obj.bar() // bar

上边代码为TBar的foo方法起了小名aliasFoo,于是MyClass也能够混入TBar的foo方法了。

alias和excludes方法,能够结合起来使用。

@traits(TExample::excludes('foo','bar')::alias({baz:'exampleBaz'}))
class MyClass {}

下面代码排除了TExample的foo方法和bar方法,为baz方法起了小名exampleBaz。

as方法规为地方的代码提供了另一种写法。

@traits(TExample::as({excludes:['foo', 'bar'], alias: {baz: 'exampleBaz'}}))
class MyClass {}

Babel转码器的扶助

时下,Babel转码器已经支撑Decorator。

首先,安装babel-corebabel-plugin-transform-decorators。由于膝下包涵在babel-preset-stage-0在那之中,所以改为设置babel-preset-stage-0亦可。

$ npm install babel-core babel-plugin-transform-decorators

接下来,设置配置文件.babelrc

{
  "plugins": ["transform-decorators"]
}

那会儿,Babel就足以对Decorator转码了。

本子中展开的下令如下。

babel.transform("code", {plugins: ["transform-decorators"]})

Babel的官网提供一个在线转码器,只要勾选Experimental,就会扶持Decorator的在线转码。

本文由新金沙国际手机版发布于www.6165.com,转载请注明出处:修饰器

关键词:

MyEclipse和Eclipse各个版本的汉化破解(包括7.5-9.0和

那是先前公布在本人的百度空间上的一篇作品,明天搬移到网易了。呵呵呵。终究,新浪是来做才能的。小编个人那...

详细>>