裝飾器的流行應該感謝在Angular 2+中使用,在Angular中,裝飾器因TypeScript能使用。可是在JavaScript中,還處於提議階段。本文將介紹裝飾器是什麼,及裝飾器如何讓代碼更加簡潔和容易理解。javascript
裝飾器是用一個代碼包裝另外一個代碼的簡單方式。java
這個概念與以前所聽過的函數複合和高階組件類似。react
這已經用於不少狀況,就是簡單的將一個函數包裝成另外一個函數:git
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);複製代碼
上個例子產生新函數wrapped
,此函數與doSomething
作一樣事情,可是他們不一樣在於在包裝函數以前和以後輸出一些語句。es6
doSomething('Graham');
// Hello, Graham
wrapped('Graham');
// Starting
// Hello, Graham
// Finished複製代碼
JavaScript中裝飾器使用特殊的語法,使用@
做爲標識符,且放置在被裝飾代碼以前。github
注意:如今裝飾器還處於提議階段,意味着還有能夠變化之處npm
能夠放置許多裝飾器在一樣代碼以前,而後解釋器會按照順序相應執行數組
@log()
@immutable()
class Example {
@time('demo')
doSomething() {
}
}複製代碼
上例中定義了一個類,採用了三個裝飾器:兩個用於類自己,一個用於類的屬性:瀏覽器
@log
能記錄全部全部訪問類@immutable
讓類不可變-也許新實例調用了Object.freeze
@time
會記錄一個方法從執行到輸出一個獨特標籤如今,雖然如今瀏覽器或Node還沒支持。可是若是使用Babel,能使用 transform-decorators-legacy插件使用裝飾器。babel
插件中使用legacy是由於Babel 5支持處理裝飾器,可是它也許會跟最終的標準有區別,因此才使用legacy這個詞。
函數複合在JavaScript已經成爲可能,可是它至關困難或不可能用於另外一個代碼(如類或類屬性)。
裝飾器提議能夠用於類或屬性,將來JavaScript版本可能會增長用於其餘地方。
裝飾器也考慮到採用較爲簡潔的語法。
如今,裝飾器只支持類和類屬性,這包含屬性、方法、get函數和set函數
裝飾器只會在程序第一次運行時執行一次,裝飾的代碼會被返回的值代替
屬性裝飾器適用於類的單獨成員-不管是屬性、方法、get函數或set函數。
裝飾器函數調用三個參數:
Object.defineProperty
@readonly
是經典的例子:
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}複製代碼
上例會將成員描述符中的writable
設爲false
。
接着用於類中屬性:
class Example {
a() {}
@readonly
b() {}
}
const e = new Example();
e.a = 1;
e.b = 2;
// TypeError: Cannot assign to read only property 'b' of object '#<Example>'複製代碼
可是咱們能夠作的更好,能夠用別的形式代替裝飾函數。例如,記錄全部的輸入和輸出:
function log(target, name, descriptor) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function(...args) {
console.log(`Arguments: ${args}`);
try {
const result = original.apply(this, args);
console.log(`Result: ${result}`);
return result;
} catch (e) {
console.log(`Error: ${e}`);
throw e;
}
}
}
return descriptor;
}複製代碼
注意咱們使用了擴展運算符,會自動將全部參數轉爲數組。
class Example {
@log
sum(a, b) {
return a + b;
}
}
const e = new Example();
e.sum(1, 2);
// Arguments: 1,2
// Result: 3複製代碼
可讓裝飾器獲取一些參數,例如重寫log
裝飾器以下:
function log(name) {
return function decorator(t, n, descriptor) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function(...args) {
console.log(`Arguments for ${name}: ${args}`);
try {
const result = original.apply(this, args);
console.log(`Result from ${name}: ${result}`);
return result;
} catch (e) {
console.log(`Error from ${name}: ${e}`);
throw e;
}
}
}
return descriptor;
};
}複製代碼
這與以前的log
裝飾器相同,只是利用了外部函數的name
參數。
class Example {
@log('some tag')
sum(a, b) {
return a + b;
}
}
const e = new Example();
e.sum(1, 2);
// Arguments for some tag: 1,2
// Result from some tag: 3複製代碼
類裝飾器用於整個類,裝飾器函數的參數爲被裝飾的構造器函數。
注意只用於構造器函數,而不適用於類的每一個實例。這就意味着若是想控制實例,就必須返回一個包裝版本的構造器函數。
一般,類裝飾器沒什麼用處,由於你所須要作的,一樣能夠用一個簡單函數來處理。你所作的只須要在結束時返回一個新的構造函數來代替類的構造函數。
回到咱們記錄那個例子,編寫一個記錄構造函數參數:
function log(Class) {
return (...args) => {
console.log(args);
return new Class(...args);
};
}複製代碼
這裏接收一個類做爲參數,返回新函數做爲構造器。此函數打印出參數,返回這些參數構造的實例。
例如:
@log
class Example {
constructor(name, age) {
}
}
const e = new Example('Graham', 34);
// [ 'Graham', 34 ]
console.log(e);
// Example {}複製代碼
構造Example
類時會輸出提供的參數,構造值e
也確實是Example
的實例。
傳遞參數到類裝飾器與類成員同樣。
function log(name) {
return function decorator(Class) {
return (...args) => {
console.log(`Arguments for ${name}: args`);
return new Class(...args);
};
}
}
@log('Demo')
class Example {
constructor(name, age) {}
}
const e = new Example('Graham', 34);
// Arguments for Demo: args
console.log(e);
// Example {}複製代碼
Core decorators是一個庫,提供了幾個常見的修飾器,經過它能夠更好地理解修飾器。
想理解此庫,也能夠去看看阮老師的關於此庫的介紹
React普遍運用了高階組件,這讓React組件成爲一個函數,而且能包含另外一個組件。
使用裝飾器是不錯的替代法,例如,Redux庫有一個connect
函數,用於鏈接React組件和React store。
一般,是這麼使用的:
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);複製代碼
然而,可使用裝飾器代替:
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}複製代碼
JavaScript Decorators: What They Are and When to Use Them
阮老師ES6入門-修飾器