詳解ES7 Decorator 入門解析
Decorator 提供了一種獨(dú)特的抽象邏輯,可在原有代碼基礎(chǔ)上,零侵入添加新功能特性。商業(yè)代碼總是多種交織并存的,在日常開發(fā)中,除了實(shí)現(xiàn)業(yè)務(wù)功能之外,我們還需要考慮諸如:異常處理、性能分析、日志等額外的需求。未經(jīng)設(shè)計(jì)的的開發(fā)方法會(huì)傾向于將各種需求耦合組成一個(gè)功能模塊,比如:
class Math{
static add(num1,num2){
try{
console.time('some label');
log('log for something');
const result= num1+num2;
console.timeEnd('some label');
return result;
}catch(e){
error('something had broken');
}
}
}
上述簡(jiǎn)單的兩數(shù)相加功能,在添加各類需求之后,已經(jīng)變的面目全非。Decorator 語(yǔ)法通過(guò)描述,可將功能特性疊加到原有功能中:
class Math{
@log
@error
@time
static add(num1,num2){
return num1+num2;
}
}
Decorator 是什么
Decorator 就是一個(gè)的包裹函數(shù),運(yùn)行時(shí)在編譯階段調(diào)用該函數(shù),修改目標(biāo)對(duì)象的行為、屬性。我們先來(lái)看一個(gè)簡(jiǎn)單實(shí)例:
const log = (target,prop)=>console.log(`Wrap function: '${prop}'`);
const tec={
@log
say(){
console.log('hello world')
}
}
// => Wrap function 'say'
Decorator 函數(shù)簽名如下:
// @param target 作用對(duì)象
// @param prop 作用的屬性名
// @param descriptor 屬性描述符
// @return descriptor 屬性描述符
function decorator(target,prop,descriptor){}
參數(shù)詳解:
- target : 作用的對(duì)象,有如下情況:
- 作用于 class 時(shí),target 為該 class 函數(shù)
- 作用于 class 中的函數(shù)、屬性 時(shí),target 為該 class 的 prototype 對(duì)象
- 作用于 對(duì)象字面量中的函數(shù)、屬性 時(shí),target 為該對(duì)象
- prop : 描述的屬性名,若decorator作用于class時(shí),該參數(shù)為空
- descriptor : 屬性原本的描述符,該描述符可通過(guò)Object.getOwnPropertyDescriptor() 獲取,若decorator作用于class時(shí),該參數(shù)為空
- decorator 函數(shù)支持返回描述符或 undefined,當(dāng)返回值為描述符時(shí),運(yùn)行時(shí)會(huì)調(diào)用Object.defineProperty()修改原有屬性。
Decorator 的ES5實(shí)現(xiàn)
理解 Decorator 機(jī)制,最佳方式是使用ES5實(shí)現(xiàn)該過(guò)程。
class裝飾器機(jī)制比較簡(jiǎn)單,僅做一層包裝,偽代碼:
// 調(diào)用實(shí)例
@log
class Person{}
// 實(shí)現(xiàn)代碼
const Person = log(Person);
屬性裝飾器機(jī)制則比較復(fù)雜,babel 就此提供了一個(gè)參考范例:
// decorator 處理
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
var desc = {};
Object['ke' + 'ys'](descriptor).forEach(function (key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ('value' in desc || desc.initializer) {
desc.writable = true;
}
desc = decorators.slice().reverse().reduce(function (desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object['define' + 'Property'](target, property, desc);
desc = null;
}
return desc;
}
// 調(diào)用實(shí)例
class Person{
@log
say(){}
}
// 實(shí)現(xiàn)代碼
_applyDecoratedDescriptor(
Person.prototype,
'say',
[log],
Object.getOwnPropertyDescriptor(Person.prototype, 'say'),
Person.prototype)
)
用例
Decorator 主要應(yīng)用于如下幾類對(duì)象:
- class
- class 中,除構(gòu)造函數(shù)外的方法
- class 中的屬性
- 對(duì)象字面量中的函數(shù)
- 對(duì)象字面量中的屬性
// 類
@log
class Person{
// 函數(shù)
@log
say(){}
// 屬性
@log
name = 'tec';
}
// 同樣適用于對(duì)象字面量的方法、屬性
const tec = {
@log
name:'tec',
@log
walk(){}
};
Decorator 實(shí)踐
在JS中,Decorator 是一個(gè)新概念,對(duì)于多數(shù)沒(méi)有接觸過(guò)諸如python、C#的開發(fā)者而言,很難理解實(shí)際應(yīng)用場(chǎng)景。幸運(yùn)的是github已經(jīng)有人封裝了常用Decorator。筆者分析該庫(kù),總結(jié)如下幾種定義模式:
通過(guò) descriptor 的 value 值修改:
function decorate(target, key, descriptor) {
const fn = descriptor.value;
return {
...descriptor,
value() {
return fn.apply(this, arguments);
}
}
}
通過(guò) descriptor 的 get、set 函數(shù)修改:
function decorate(target, key, descriptor) {
let value = descriptor.value;
return {
...descriptor,
get() {
return value;
}
set(v) {
value=v;
}
}
}
通過(guò) descriptor 的 writable、enumerable 等屬性修改:
function readonly(target, key, descriptor) {
return {
...descriptor,
writable:false
}
}
針對(duì) class ,返回包裹函數(shù)
function log(target){
let initTimes=0;
return (...arg)=>{
console.log(++initTimes);
target.call(this,...arg);
};
}
在實(shí)際開發(fā)中,還需要注意以下事項(xiàng):
- Decorator 的目標(biāo)是在原有功能基礎(chǔ)上,添加功能,切忌覆蓋原有功能
- Decorator 不是管道模式,decorator之間不存在交互,所以必須注意保持decorator獨(dú)立性、透明性
- Decorator 更適用于非業(yè)務(wù)功能需求
- 確定 decorator 的用途后,切記執(zhí)行判斷參數(shù)類型
- decorator 針對(duì)每個(gè)裝飾目標(biāo),僅執(zhí)行一次
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
window.onload與$(document).ready()的區(qū)別分析
這篇文章主要介紹了window.onload與$(document).ready()的區(qū)別,實(shí)例分析了二者在加載頁(yè)面元素使用過(guò)程中的區(qū)別,需要的朋友可以參考下2015-05-05
設(shè)置jsf的選擇框h:selectOneMenu為不可編輯狀態(tài)的方法
本文為大家詳細(xì)介紹下如何設(shè)置jsf的選擇框h:selectOneMenu為不可編輯狀態(tài),具體實(shí)現(xiàn)代碼如下,希望對(duì)大家有所幫助2014-01-01
jqplot通過(guò)ajax動(dòng)態(tài)畫折線圖的方法及思路
這篇文章主要介紹了2013-12-12
靈活使用console讓js調(diào)試更簡(jiǎn)單的方法步驟
這篇文章主要介紹了靈活使用console讓js調(diào)試更簡(jiǎn)單的方法步驟,適當(dāng)使用這些方法可以使調(diào)試更容易,更快速,更直觀,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04
nullJavascript中創(chuàng)建對(duì)象的五種方法實(shí)例
今天上午,非常郁悶,有很多簡(jiǎn)單基礎(chǔ)的問(wèn)題搞得我有些迷茫,哎,代碼幾天不寫就忘。目前又不當(dāng)COO,還是得用心記代碼哦!2013-05-05
使用BootStrap實(shí)現(xiàn)用戶登錄界面UI
本文給大家介紹使用BootStrap實(shí)現(xiàn)用戶登錄界面UI,布局風(fēng)格采用左右各一半的風(fēng)格設(shè)計(jì),非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-08-08
使用json來(lái)定義函數(shù),在里面可以定義多個(gè)函數(shù)的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇使用json來(lái)定義函數(shù),在里面可以定義多個(gè)函數(shù)的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
Javascript中 帶名 匿名 箭頭函數(shù)的重要區(qū)別(推薦)
這篇文章主要介紹了Javascript中 帶名 匿名 箭頭函數(shù)的重要區(qū)別,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-01-01
JS實(shí)現(xiàn)關(guān)閉當(dāng)前頁(yè)而不彈出提示框的方法
這篇文章主要介紹了JS實(shí)現(xiàn)關(guān)閉當(dāng)前頁(yè)而不彈出提示框的方法,結(jié)合實(shí)例形式分析了JS操作頁(yè)面的打開、關(guān)閉及父頁(yè)面的關(guān)閉技巧,需要的朋友可以參考下2016-06-06

