ES6之修飾器Decorator
- 類的修飾
許多面向物件的語言都有修飾器(Decorator)函式,用來修改類的行為。
@testable class MyTestableClass { // ... } function testable(target) { target.isTestable = true; } MyTestableClass.isTestable // true 上面程式碼中,@testable就是一個修飾器。它修改了MyTestableClass這個類的行為,為它加上了靜態屬性isTestable。testable函式的引數target是MyTestableClass類本身。
修飾器是一個對類進行處理的函式。修飾器函式的第一個引數,就是所要修飾的目標類。如果覺得一個引數不夠用,可以在修飾器外面再封裝一層函式。
//testable函式的引數target,就是會被修飾的類。 function 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物件上新增屬性,因此就可以在例項上呼叫。
React 與 Redux 庫結合使用時,常常需要寫成下面這樣。
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
有了裝飾器,就可以改寫上面的程式碼。
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
- 方法的修飾
修飾器函式readonly一共可以接受三個引數。
第一個引數是類的原型物件,上例是Person.prototype,修飾器的本意是要“修飾”類的例項,但是這個時候例項還沒生成,所以只能去修飾原型(這不同於類的修飾,那種情況時target引數指的是類本身);
第二個引數是所要修飾的屬性名;
第三個引數是該屬性的描述物件。
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),然後被修改的描述物件再用來定義屬性。
@log修飾器,可以起到輸出日誌的作用
@log修飾器的作用就是在執行原始的操作之前,執行一次console.log,從而達到輸出日誌的目的。
修飾器有註釋的作用。
如果同一個方法有多個修飾器,會像剝洋蔥一樣,先從外到內進入,然後由內向外執行。
修飾器還能用來型別檢查。所以,對於類來說,這項功能相當有用。從長期來看,它將是 JavaScript 程式碼靜態分析的重要工具。
- 修飾器不能用去函式
修飾器只能用於類和類的方法,不能用於函式,因為存在函式提升。由於存在函式提升,使得修飾器不能用於函式。類是不會提升的,所以就沒有這方面的問題。
另一方面,如果一定要修飾函式,可以採用高階函式的形式直接執行。
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是一個第三方模組,提供了幾個常見的修飾器,通過它可以更好地理解修飾器。
(1)@autobind
autobind修飾器使得方法中的this物件,繫結原始物件。
(2)@readonly
readonly修飾器使得屬性或方法不可寫。
(3)@override
override修飾器檢查子類的方法,是否正確覆蓋了父類的同名方法,如果不正確會報錯。
(4)@deprecate (別名@deprecated)
deprecate或deprecated修飾器在控制檯顯示一條警告,表示該方法將廢除。
(5)@suppressWarnings
suppressWarnings修飾器抑制deprecated修飾器導致的console.warn()呼叫。但是,非同步程式碼發出的呼叫除外。
- 修飾器實現自動釋出事件
我們可以使用修飾器,使得物件的方法被呼叫時,自動發出一個事件。
只要呼叫someMethod或者anotherMethod,就會自動發出一個事件。
// index.js
import publish from './publish';
class FooComponent {
@publish('foo.some.message', 'component')
someMethod() {
return { my: 'data' };
}
@publish('foo.some.other')
anotherMethod() {
// ...
}
}
let foo = new FooComponent();
foo.someMethod();
foo.anotherMethod();
以後,只要呼叫someMethod或者anotherMethod,就會自動發出一個事件。
$ bash-node index.js
頻道: component
事件: foo.some.message
資料: { my: 'data' }
頻道: /
事件: foo.some.other
資料: undefined
- Mixin
在修飾器的基礎上,可以實現Mixin模式。所謂Mixin模式,就是物件繼承的一種替代方案,中文譯為“混入”(mix in),意為在一個物件之中混入另外一個物件的方法。
如果需要“混入”多個方法,就生成多個混入類。
class MyClass extends Mixin1(Mixin2(MyBaseClass)) {
/* ... */
}
這種寫法的一個好處,是可以呼叫super,因此可以避免在“混入”過程中覆蓋父類的同名方法。
- Trait
Trait 也是一種修飾器,效果與 Mixin 類似,但是提供更多功能,比如防止同名方法的衝突、排除混入某些方法、為混入的方法起別名等等。
Trait 不允許“混入”同名方法。
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-core和babel-plugin-transform-decorators。由於後者包括在babel-preset-stage-0之中,所以改為安裝babel-preset-stage-0亦可。
然後,設定配置檔案.babelrc。
//首先,安裝babel-core和babel-plugin-transform-decorators
$ npm install babel-core babel-plugin-transform-decorators
//然後,設定配置檔案.babelrc
{
"plugins": ["transform-decorators"]
}
//這時,Babel 就可以對 Decorator 轉碼了。
//指令碼中開啟的命令如下。
babel.transform("code", {plugins: ["transform-decorators"]})