簡介
在一些框架(比如 nest.js、midway.js 等)中,經(jīng)??吹皆陬惔a的附近有 @...... 這種代碼,這就是 Decorator
Decorator 可以叫做修飾器,或者是裝飾器
修飾器是一種特殊類型的聲明,它只能夠被附加到類的聲明、方法、屬性或參數(shù)上,可以修改類的行為。但不能用于函數(shù)(因為存在函數(shù)提升)
常見的修飾器有:類修飾器、屬性修飾器、方法修飾器、參數(shù)修飾器
修飾器寫法:普通修飾器(無法傳參)、修飾器工廠(可傳參)
修飾器對類行為的改變,是代碼編譯時發(fā)生的,而不是在運行時。修飾器能在編譯階段運行代碼。也就是說,修飾器本質(zhì)就是編譯時執(zhí)行的函數(shù)
修飾器是一個對類進行處理的函數(shù)。修飾器函數(shù)的第一個參數(shù),就是所要修飾的目標類
修飾器的行為類似如下
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A
類修飾器
類修飾器在類聲明之前被聲明(緊靠著類聲明)
類修飾器應(yīng)用于類構(gòu)造函數(shù),可以用來監(jiān)視,修改或替換類定義
- 類修飾器示例
function logClass (target) {
console.log(target) // [Function: MyTest] target指向修飾的類
target.nTest = '擴展的靜態(tài)屬性' // 擴展靜態(tài)屬性
target.prototype.nName = '動態(tài)擴展的屬性' // 給原型擴展屬性
target.prototype.nFun = () => {
console.log('動態(tài)擴展的方法')
}
}
@logClass
class MyTest {}
const test = new MyTest()
console.log(test.nTest) // 擴展的靜態(tài)屬性
console.log(test.nName) // 動態(tài)擴展的屬性
test.nFun() // 動態(tài)擴展的方法
- 修飾器工廠(閉包傳參)
function logClass (params) {
return function (target) {
console.log(target) // [Function: MyTest]
console.log(params) // hello
target.prototype.nName = '動態(tài)擴展的屬性'
target.prototype.nFun = () => {
console.log('動態(tài)擴展的方法')
}
}
}
@logClass('hello')
class MyTest {}
const test = new MyTest()
console.log(test.nName) // 動態(tài)擴展的屬性
test.nFun() // 動態(tài)擴展的方法
- Mixins 混入例子
// mixins.js 可以返回一個函數(shù)
export function mixins(...list) {
return function(target) {
Object.assign( target.prototype, ...list )
}
}
// main.js
import { mixins } from './mixins.js'
const Foo = {
foo() {console.log('foo')}
}
@mixins(Foo) // 當函數(shù)調(diào)用,傳入?yún)?shù)
class MyClass {}
const obj = new MyClass()
obj.foo // 'foo'
- 重載構(gòu)造函數(shù)的例子
function logClass (target) {
return class extends target {
attr = '重載屬性'
getData () {
console.log('重載方法', this.attr)
}
}
}
@logClass
class MyTest {
attr
constructor () {
this.attr = '構(gòu)造函數(shù)的屬性'
}
getData () {
console.log(this.attr)
}
}
const test = new MyTest()
test.getData() // 重載方法 重載屬性
-
React+Redux例子
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
// 可改寫成
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
屬性修飾器
屬性裝飾器表達式會在運行時當作函數(shù)被調(diào)用,傳入下列 2 個參數(shù)
- 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象
- 修飾的屬性名
function logProperty (params) {
return function (target, name) {
console.log(target, name) // MyTest { getData: [Function] } 'attr'
target[name] = params
}
}
class MyTest {
@logProperty('屬性修飾器的參數(shù)')
attr
constructor () {}
getData () {
console.log(this.attr) // 屬性修飾器的參數(shù)
}
}
const test = new MyTest()
test.getData()
方法修飾器
它會被應(yīng)用到方法的屬性描述符上,可以用來監(jiān)聽、修改或者替換方法定義
修飾器會修改屬性的描述對象,然后被修改的描述對象再用來定義屬性
方法修飾器會在運行時傳入下列 3 個參數(shù):
- 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象
- 修飾的屬性名
- 該屬性的描述對象
- 方法修飾器示例
// 方法修飾器
function logFunction (params) {
return function (target, methodName, desc) {
console.log(target, methodName, desc)
/*
MyTest { getData: [Function] }
'getData'
{
value: [Function], // 值
writable: true, // 可讀
enumerable: true, // 可枚舉
configurable: true // 可設(shè)置
}
*/
target.nName = '動態(tài)擴展的屬性'
target.nFun = () => {
console.log('動態(tài)擴展的方法')
}
// 將接收到的參數(shù)改為 string 類型
const oMethod = desc.value
desc.value = function (...args) {
args = args.map((v) => {
return String(v)
})
return oMethod.apply(this, args)
}
}
}
class MyTest {
@logFunction('方法修飾器的參數(shù)')
getData (...args) {
console.log(args)
}
}
const test = new MyTest()
console.log(test.nName) // 動態(tài)擴展的屬性
test.nFun() // 動態(tài)擴展的方法
test.getData(123, '234', () => {}) // [ '123', '234', 'function () { }' ]
- 輸出日志的例子
class Math {
@log
add(a, b) {
return a + b;
}
}
// @log修飾器的作用就是在執(zhí)行原始的操作之前,執(zhí)行一次console.log,從而達到輸出日志的目的
function log(target, name, descriptor) {
const oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}
const math = new Math();
// passed parameters should get logged now
math.add(2, 4);
- 修飾器有注釋的作用,看上去一目了然
@Component({
tag: 'my-component',
styleUrl: 'my-component.scss'
})
export class MyComponent {
@Prop() first: string; // props
@Prop() last: string; // props
@State() isVisible: boolean = true; // props
render() {
return (
<p>Hello, my name is {this.first} {this.last}</p>
);
}
}
參數(shù)修飾器
參數(shù)修飾器表達式會在運行時當作函數(shù)被調(diào)用,可以使用參數(shù)修飾器為類的原型增加一些元素數(shù)據(jù),傳入下列 3 個參數(shù)
- 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象
- 參數(shù)的名字
- 參數(shù)在函數(shù)參數(shù)列表中的索引
function logParams (params) {
return function (target, methodName, paramsIndex) {
// MyTest { getData: [Function] } 'getData' 0
console.log(target, methodName, paramsIndex)
target.param = params
}
}
class MyTest {
getData (@logParams('參數(shù)修飾符的參數(shù)') id) {
console.log(id)
}
}
const test = new MyTest()
test.getData(123)
console.log(test.param) // 參數(shù)修飾符的參數(shù)
執(zhí)行順序
屬性修飾器 > 參數(shù)修飾器 > 方法修飾器 > 類修飾器
function log (params) {
return function () {
console.log(params)
}
}
@log('類修飾器')
class MyTest {
@log('屬性修飾器')
id
@log('方法修飾器')
getData (@log('參數(shù)修飾器') id) {
this.id = id
}
}
new MyTest()
- 同一個方法有多個修飾器,會像剝洋蔥一樣,先從外到內(nèi)進入,然后由內(nèi)向外執(zhí)行
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
不能用于函數(shù)
修飾器只能用于類和類的方法,不能用于函數(shù),因為存在函數(shù)提升。
// 意圖是執(zhí)行后counter等于 1,但是實際上結(jié)果是counter等于 0
var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
上面的代碼,函數(shù)提升后相當于
@add
function foo() {
}
var counter;
var add;
counter = 0;
add = function () {
counter++;
};