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"