1. 程式人生 > >ES6 -- 修飾器

ES6 -- 修飾器

decorator是ES7引入的功能,它是一個函式,用來修改類甚至於是方法的行為。

類的修飾

一個簡單的栗子:

@testable
class MyTestableClass {
  // ...
}

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

MyTestableClass.isTestable // true

上面的栗子中,@testable就是一個修飾器,它修改了它下面的這個MyTestableClass類的行為:給它加上了一個靜態屬性isTestable。

注意,修飾器對類的行為的改變,是在程式碼編譯的時候就發生的,而不是在程式碼執行時。
這就意味著,修飾器能夠在編譯階段執行程式碼。也就是說,修飾器本質就是編譯時執行的函式

如果覺得一個引數不夠用,可以在修飾器外面再封裝一層函式

例子:

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

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

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

在這個例子中,修飾器可以接受引數,以控制修飾器的行為。

前面的例子都是為類新增一個靜態屬性,當然,我們也可以通過為目標類的prototype新增屬性來新增例項屬性。

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

@testable
class MyTestableClass {}

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

這樣通過prototype新增的屬性,就可以在例項上呼叫。

下面是另一個例子,在這個栗子中,包含export和import程式碼,並且還加入了閉包的思想。

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

這就是上一篇文件中的mixin:將Foo類的方法新增到了MyClass的例項上面。

方法的修飾

修飾器不僅可以修飾類,還可以修飾類的屬性。

例子:

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

此時,修飾器函式一共可以接受三個引數:第一個引數是要修飾的目標物件,第二個引數是要修飾的屬性名,第三個引數是該屬性的描述物件。

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

修飾器會修改屬性的描述物件,然後被修改的描述物件再用來定義屬性。

這是另一個例子:

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

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

修飾器還有註釋的作用:

@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

修飾器不能用於函式

修飾器只能用於“類”和類的方法,不能用於函式,因為存在函式提升。

如果真的需要修飾函式,可以採用高階函式的形式直接 執行。

function doSomething(name) {
  console.log('Hello, ' + name);
}

function loggingDecorator(wrapped) {
  return function() {
    console.log('Starting');
    const result = wrapped.apply(this, arguments);
    console.log('Finished');
    return result;
  }
}

const wrapped = loggingDecorator(doSomething);

core-decorators.js

core-decorators.js是一個第三方模組,提供了幾個常見的修飾器,通過它可以更好地理解修飾器。

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'

這是一個混入的簡單實現,下面我們用修飾器來實現它:

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"