ECMAScript 6-修飾器Decorator
修飾器(Decorator)是一個函式,用來修改類的行為。修飾器對類的行為的改變,是程式碼編譯時發生的,而不是在執行時。這意味著,修飾器能在編譯階段執行程式碼
類的修飾
//修飾器函式1:為類加上靜態屬性isTestable //修飾器函式的第一個引數,就是所要修飾的目標類 function testable(target) { target.isTestable = true; } //修飾類的行為。寫在類上方,表示修飾器的目標是這整個類 @testable class MyTestableClass {} console.log(MyTestableClass.isTestable) // true
//修飾器函式2:新增例項屬性
function testable(target) {
target.prototype.isTestable = true;
}
@testable
class MyTestableClass {}
let obj = new MyTestableClass();
obj.isTestable // true
修飾器的實際行為
@decorator
class A {}
// 等同於
class A {}
A = decorator(A) || A;
類的方法的修飾
class Person { @readonly name() { return `${this.first} ${this.last}` } }
修飾器的引數
修飾器函式一共可以接受三個引數:
- 所要修飾的目標物件
- 所要修飾的屬性名
- 該屬性的描述物件
function nonenumerable(target, name, descriptor) {
descriptor.enumerable = false; //修改了描述物件的屬性enumerable,使得不可遍歷
return descriptor;
}
class Person {
@nonenumerable //修飾該屬性
get kidCount() { return this.children.length; }
}
@log修飾器例子:輸出日誌
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; } class Math { @log add(a, b) { return a + b; } } const math = new Math(); // passed parameters should get logged now math.add(2, 4);
修飾器有註釋作用
@testable
class Person {
@readonly
@nonenumerable
name() { return `${this.first} ${this.last}` }
}
//可看出Person類是可測試的,而name方法是隻讀和不可列舉的
core-decorators.js
core-decorators.js是一個第三方模組,提供了幾個常見的修飾器,通過它可以更好地理解修飾器
-
@autobind:使得方法中的
this
物件,繫結原始物件import { autobind } from 'core-decorators'; class Person { @autobind getPerson() { return this; } } let person = new Person(); let getPerson = person.getPerson; getPerson() === person; // true
-
@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]
-
@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) }
-
@deprecate (別名@deprecated):在控制檯顯示一條警告,表示該方法將廢除
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. //
Mixin
在修飾器的基礎上,可以實現Mixin
模式。所謂Mixin
模式,就是物件繼承的一種替代方案,中文譯為“混入”(mixin),意為在一個物件之中混入另外一個物件的方法。
以往寫法:
//一個物件,裡面有個foo方法
const Foo = {
foo() { console.log('foo') }
};
//一個類
class MyClass {}
//通過Object.assign在類的原型物件上新增Foo物件上的方法新增進去,
Object.assign(MyClass.prototype, Foo);
let obj = new MyClass();
obj.foo() // '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) //傳入Foo物件,把Foo物件中foo方法的新增在MyClass的原型物件中
class MyClass {}
let obj = new MyClass();
obj.foo() // "foo"
Trait修飾器
效果與Mixin類似,但是提供更多功能,比如防止同名方法的衝突、排除混入某些方法、為混入的方法起別名等等
下面採用traits-decorator這個第三方模組作為例子。這個模組提供的traits修飾器,不僅可以接受物件,還可以接受ES6類作為引數
import { traits } from 'traits-decorator';
//TFoo類中有foo方法
class TFoo {
foo() { console.log('foo') }
}
//TBar物件中有bar方法
const TBar = {
bar() { console.log('bar') }
};
//把TFoo類中有foo方法和TBar物件中有bar方法加進MyClass類的原型物件中
@traits(TFoo, TBar)
class MyClass { }
let obj = new MyClass();
obj.foo() // foo
obj.bar() // bar
另外,Trait不允許“混入”同名方法
Babel轉碼器的支援
目前,Babel轉碼器已經支援Decorator。
首先,安裝babel-core
和babel-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"]})