Decorator 裝飾器
大廠技術(shù)??高級前端??Node進階
點擊上方?程序員成長指北,關(guān)注公眾號
回復(fù)1,加入高級Node交流群
前言
大家在前端開發(fā)過程中有遇到過 @ + 方法名 這種寫法嗎?當我第一次看到的時候,直接懵了,這是什么東東……
遇到困難解決困難,在我的一番查找后,我知道了,原來這東西叫裝飾器,英文名叫 Decorator ,那它到底是干什么的呢?接下來就讓我跟大家說道說道~
什么是裝飾器
裝飾者模式
裝飾者模式就是能夠在不改變對象自身的基礎(chǔ)上,在程序運行期間給對象動態(tài)地添加職責(zé)。打個比方,一個人在天氣冷的時候要穿棉衣,天氣熱的時候穿短袖,可無論穿什么,本質(zhì)上他還是一個人,只不過身上穿了不同的衣服。
所以簡單來說, Decorator 就是一種動態(tài)地往一個類中添加新的行為的設(shè)計模式, 它可以在類運行時, 擴展一個類的功能, 并且去修改類本身的屬性和方法, 使其可以在不同類之間更靈活的共用一些屬性和方法。
@ 是針對這種設(shè)計模式的一個語法糖,不過目前還處于第 2 階段提案中,使用它之前需要使用 Babel 模塊編譯成 ES5 或 ES6。
怎么使用裝飾器
三方庫使用
Babel 版本 ≥ 7.x
如果項目的 Babel 版本大于等于 7.x,那么可以使用 @babel/plugin-proposal-decorators
安裝
npm?install?--save-dev?@babel/plugin-proposal-decorators配置 .babelrc
{
??"plugins":?[
????["@babel/plugin-proposal-decorators",?{?"legacy":?true?}],
??]
}
Babel 版本 ≤ 6.x
如果小于等于 6.x,則可以使用 babel-plugin-transform-decorators-legacy
安裝
npm?install?--save-dev?@babel/plugin-proposal-decorators配置 .babelrc
{
????"plugins":?["transform-decorators-legacy"]
}
使用方法
裝飾器的寫法是 @ + 返回裝飾器函數(shù)的表達式,所以其使用方法如下:
@classDecorator
class?TargetClass?{?//?類
??@fieldDecorator
??targetField?=?0;?//?類實例屬性
??@funDecorator
??targetFun()?{?}?//?類方法
??@accessorDecorator
??get?targetGetFun()?{?}?//?類訪問器
}
如果一個對象使用多個裝飾器,那么執(zhí)行順序是什么呢?
function?decorator1()?{
??console.log('decorator1');
??return?function?decFn1(targetClass)?{
????console.log('decFn1');
????return?targetClass;
??};
}
function?decorator2()?{
??console.log('decorator2');
??return?function?decFn2(targetClass)?{
????console.log('decFn2');
????return?targetClass;
??};
}
執(zhí)行順序:

打印結(jié)果:

根據(jù)以上,我們可知,裝飾器的執(zhí)行順序為由外向內(nèi)進入,由內(nèi)向外執(zhí)行。
使用范圍
根據(jù)使用方法,我們可以看出裝飾器可以應(yīng)用于以下幾種類型:
類(class) 類實例屬性(公共、私有和靜態(tài)) 類方法(公共、私有和靜態(tài)) 類訪問器(公共、私有和靜態(tài))
函數(shù)的裝飾
當我們看完裝飾器的使用方法和使用范圍時,我們發(fā)現(xiàn),裝飾器不能修飾函數(shù),那原因到底是什么呢?原因就是函數(shù)有函數(shù)提升。
var?num?=?0;
function?add?()?{
??num?++;
}
@add
function?fn()?{}
在這個例子中,我們想要在執(zhí)行后讓 num 等于 1,但其實結(jié)果并不是這樣,因為函數(shù)提升,實際上代碼是這樣執(zhí)行的:
function?add?()?{
??num?++;
}
@add
function?fn()?{}
var?num;
num?=?0;
如果一定要裝飾函數(shù)的話,可以采用高階函數(shù)的形式,這篇文章主要講裝飾器,有關(guān)高階函數(shù)就不在此贅述了,不了解的小伙伴們可自行查閱資料哈~
裝飾器原理
根據(jù)裝飾器的使用范圍,可以把它分為兩大類:類的裝飾與類方法的裝飾,下面就讓我為大家逐個分享一下。
類的裝飾
傳參
首先我們先根據(jù)一個小例子看一下裝飾器接收參數(shù)的情況:
function?decorator(...args)?{
??args.forEach((arg,?index)?=>?{
????console.log(`參數(shù)${index}`,?arg);
??});
}
@decorator
class?TargetClass?{?}
console.log('targetClass:',?TargetClass);
打印結(jié)果如下:

看到結(jié)果,我們發(fā)現(xiàn)裝飾器只接收一個參數(shù),就是被裝飾的類定義本身。
返回值
我們繼續(xù)通過一個小例子來看返回值的情況:
function?returnStr(targetClass)?{
??return?'hello?world~';
}
function?returnClass(targetClass)?{
??return?targetClass;
}
@returnStr
class?ClassA?{?}
@returnClass
class?ClassB?{?}
console.log('ClassA:',?ClassA);
console.log('ClassB:',?ClassB);
結(jié)果如下:

根據(jù)結(jié)果,我們發(fā)現(xiàn)裝飾器返回什么輸出的就是什么。
結(jié)論
通過以上的兩個例子,我們可以得出以下這個結(jié)論:
@decorator
class?TargetClass?{?}
//?等同于
class?TargetClass?{?}
TargetClass?=?decorator(TargetClass)?||?TargetClass;
所以說,裝飾器的第一個參數(shù)就是要裝飾的類,它的功能就是對類進行處理。
類裝飾器的使用
添加屬性
因為裝飾器接收的參數(shù)就是類定義本身,所以我們可以給類添加屬性:
function?addAttribute(targetClass)?{
??targetClass.isUseDecorator?=?true;
}
@addAttribute
class?TargetClass?{?}
console.log(TargetClass.isUseDecorator);?//?true在這個例子中,我們定義了
addAttribute的裝飾器,用于對TargetClass添加isUseDecorator標記,這個用法就跟 Java 中的注解比較相似,僅僅是對目標類型打上一些標記。返回裝飾器函數(shù)的表達式
上面有說裝飾器的寫法是
@ + 返回裝飾器函數(shù)的表達式,也就是說,@后邊可以不是一個方法名,還可以是能返回裝飾器函數(shù)的表達式:function?addAttribute(content)?{
??return?function?decFn(targetClass)?{
????targetClass.content?=?content;
????return?targetClass;
??};
}
@addAttribute('這是內(nèi)容~~~')
class?TargetClass?{?}
console.log(TargetClass.content);?//?這是內(nèi)容~~~我們看到
TargetClass通過addAttribute的裝飾,添加了content這個屬性,并且可以向addAttribute傳參來給content屬性賦值,這種使用方法使裝飾器變得更加靈活。添加原型方法
在前面的例子中我們添加的都是類的靜態(tài)屬性,但是既然裝飾器接收的參數(shù)就是類定義本身,那么它也可以通過訪問類的
prototype屬性來添加或修改原型方法:function?decorator(targetClass)?{
??targetClass.prototype.decFun?=?function?()?{
????console.log('這里是裝飾器?decorator?添加的原型方法?decFun~');
??};
}
@decorator
class?TargetClass?{?}
const?targetClass?=?new?TargetClass();
console.log(targetClass);
targetClass.decFun();結(jié)果如下:

以上就是類裝飾器的使用,由此我們可以得出,裝飾器還可以對類型進行靜態(tài)標記和方法擴展,還挺有用的對吧~那么看到這里,小伙伴們是不是發(fā)現(xiàn)了在實際項目中就有類裝飾器的使用,比如 react-redux 的 connect 就是一個類裝飾器、Antd 中的 Form.create 也是一個類裝飾器。
//?connect
class?App?extends?React.Component?{}
export?default?connect(mapStateToProps,?mapDispatchToProps)(App);
//?等同于
@connect(mapStateToProps,?mapDispatchToProps)
export?default?class?App?extends?React.Component?{}
//?Form.create
const?WrappedApp?=?Form.create()(App);
//?等同于
@Form.create()
class?App?extends?React.Component?{}
類方法的裝飾
傳參
我們把類實例屬性、類方法、類訪問器都歸到這一類中的原因其實是因為它們?nèi)齻€就是作為某個對象的屬性(實例屬性、原型方法、實例訪問器屬性),也就是說它們接收的參數(shù)是類似的:
function?decorator(...args)?{
??args.forEach((arg,?index)?=>?{
????console.log(`參數(shù)${index}`,?arg);
??});
??console.log('****************');
}
class?TargetClass?{
??@decorator
??field?=?0;
??@decorator
??fn()?{?}
??@decorator
??get?getFn()?{?}
}
const?targetOne?=?new?TargetClass();
console.log(targetOne.field,?Object.getOwnPropertyDescriptor(targetOne,?'field'));
結(jié)果如下:
根據(jù)結(jié)果我們發(fā)現(xiàn),類方法裝飾器接收了三個參數(shù):類定義對象、實例屬性/方法/實例訪問器屬性名、屬性操作符。眼熟吧,沒錯,它與 Object.defineProperty() 接收的參數(shù)很像。
Object.defineProperty(obj, props, descriptor)
Object.defineProperty() 的作用就是直接在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性,并返回此對象。該方法一共接收三個參數(shù):
要定義屬性的對象(obj) 要定義或修改的屬性名或 Symbol(props)要定義或修改的屬性描述符(descriptor)
而對象里目前存在的屬性描述符有兩種主要形式:數(shù)據(jù)描述符和存取描述符。數(shù)據(jù)描述符是一個具有值的屬性,該值可以是可寫的,也可以是不可寫的;存取描述符是由 getter 函數(shù)和 setter 函數(shù)所描述的屬性。一個描述符只能是這兩者其中之一,不能同時是兩者。
它們共享以下可選鍵值:
configurable屬性是否可以被刪除和重新定義特性,默認值為
falseenumerable是否會出現(xiàn)在對象的枚舉屬性中,默認值為
false
數(shù)據(jù)描述符特有鍵值:
value該屬性對應(yīng)的值,默認值為
undefinedwritable是否可以被更改,默認值為
false
存取操作符特有鍵值:
get屬性的
getter函數(shù),如果沒有getter,則為undefined;默認為?undefinedset屬性的
setter函數(shù),如果沒有setter,則為undefined;默認為?undefined
講完 Object.defineProperty(),接下來就讓我們看看該怎么使用它吧~
類方法裝飾器的使用
讓我們通過一個例子來了解一下:
function?readonly(target,?name,?descriptor)?{
??descriptor.writable?=?false;
??return?descriptor;
}
class?Person?{
??@readonly
??name?=?'zhangsan';
}
const?person?=?new?Person();
console.log(person.name,?Object.getOwnPropertyDescriptor(person,?'name'));
打印結(jié)果如下:

上面代碼說明,裝飾器會修改屬性的描述對象,然后被修改的描述對象再用來定義屬性。
結(jié)論
由此我們可以得出結(jié)論:
function?changeName(target,?name,?descriptor)?{
??descriptor.value?=?'lisi';
??return?descriptor;
}
class?Person?{
??@changeName
??name?=?'zhangsan';
}
const?person?=?new?Person();
//?等同于
class?Person?{
??name?=?'zhangsan';
}
const?person?=?new?Person();
Object.defineProperty(person,?'name',?{
??value:?'lisi',
});
裝飾器的應(yīng)用
在項目中,可能會遇到這樣一種情況,好幾個組件的數(shù)據(jù)都是調(diào)用同一個后端接口獲得,只是傳參不同,有些小伙伴們在寫代碼的時候可能就是每個組件都去手動調(diào)用一次后端接口(以 React 項目為例):
...
export?default?class?CompOne?extends?Component?{
??...
??getData?=?async?()?=>?{?//?調(diào)用后端接口
????const?data?=?await?request('/xxx',?{
??????params:?{
????????id:?'123',?//?不同組件傳參不同
??????},
????});
????this.setState({?data?});
??}
??render()?{
????...
????return?(
??????<div>
????????...
????????我是組件一:?{data}
????????...
??????div>
????)
??}
}
遇到這種情況,我們就可以用裝飾器解決呀~
//?裝飾器
function?getData(params)?{
??return?(Comp)?=>?{
????class?WrapperComponent?extends?Component?{
??????...
??????getData?=?async?()?=>?{
????????const?data?=?await?request('/xxx',?{
??????????params,
????????});
????????this.setState({?data?});
??????}
??????render()?{
????????...
????????return?(
??????????<Comp?data={data}?/>
????????)
??????}
????}
????return?WrapperComponent;
??}
}
//?組件
...
@getData({
??id:?'123'
})
export?default?class?index?extends?Component?{
??...
??render()?{
????...
????const?data?=?this.props.data;?//?直接從?this.props?中獲取想要的數(shù)據(jù)
????return?(
??????<div>
????????...
????????我是組件一:?{data}
????????...
??????div>
????)
??}
}
總結(jié)
好啦,今天的分享就要到此結(jié)束了哦,希望通過這篇文章大家能夠?qū)ρb飾器有一定的了解,如有不同意見,歡迎在評論區(qū)評論呦~就讓暴風(fēng)雨來得更猛烈些吧!
參考鏈接
裝飾器(https://www.bookstack.cn/read/es6-3rd/docs-decorator.md)
ES7 提案: Decorators 裝飾器(https://blog.csdn.net/weixin_44691608/article/details/117180409)
Object.defineProperty()(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
babel-plugin-transform-decorators-legacy(https://www.npmjs.com/package/babel-plugin-transform-decorators-legacy)
Node 社群
我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
如果你覺得這篇內(nèi)容對你有幫助,我想請你幫我2個小忙:
1. 點個「在看」,讓更多人也能看到這篇文章 2. 訂閱官方博客?www.inode.club?讓我們一起成長 點贊和在看就是最大的支持??

