摘要:下裝飾者的實(shí)現(xiàn)了解了裝飾者模式和的概念之后,我們寫一段能夠兼容的代碼來實(shí)現(xiàn)裝飾者模式原函數(shù)拍照片定義函數(shù)裝飾函數(shù)加濾鏡用裝飾函數(shù)裝飾原函數(shù)這樣我們就實(shí)現(xiàn)了抽離拍照與濾鏡邏輯,如果以后需要自動上傳功能,也可以通過函數(shù)來添加。
什么是裝飾者模式
當(dāng)我們拍了一張照片準(zhǔn)備發(fā)朋友圈時,許多小伙伴會選擇給照片加上濾鏡。同一張照片、不同的濾鏡組合起來就會有不同的體驗(yàn)。這里實(shí)際上就應(yīng)用了裝飾者模式:是通過濾鏡裝飾了照片。在不改變對象(照片)的情況下動態(tài)的為其添加功能(濾鏡)。
需要注意的是:由于 JavaScript 語言動態(tài)的特性,我們很容易就能改變某個對象(JavaScript 中函數(shù)是一等公民)。但是我們要盡量避免直接改寫某個函數(shù),這會導(dǎo)致代碼的可維護(hù)性、可擴(kuò)展性變差,甚至?xí)廴酒渌麡I(yè)務(wù)。
什么是 AOP想必大家對"餐前洗手、飯后漱口"都不陌生。這句標(biāo)語其實(shí)就是 AOP 在生活中的例子:吃飯這個動作相當(dāng)于切點(diǎn),我們可以在這個切點(diǎn)前、后插入其它如洗手等動作。
AOP(Aspect-Oriented Programming):面向切面編程,是對 OOP 的補(bǔ)充。利用AOP可以對業(yè)務(wù)邏輯的各個部分進(jìn)行隔離,也可以隔離業(yè)務(wù)無關(guān)的功能比如日志上報、異常處理等,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高業(yè)務(wù)無關(guān)的功能的復(fù)用性,也就提高了開發(fā)的效率。
在 JavaScript 中,我們可以通過裝飾者模式來實(shí)現(xiàn) AOP,但是兩者并不是一個維度的概念。 AOP 是一種編程范式,而裝飾者是一種設(shè)計(jì)模式。
ES3 下裝飾者的實(shí)現(xiàn)了解了裝飾者模式和 AOP 的概念之后,我們寫一段能夠兼容 ES3 的代碼來實(shí)現(xiàn)裝飾者模式:
// 原函數(shù) var takePhoto =function(){ console.log("拍照片"); } // 定義 aop 函數(shù) var after=function( fn, afterfn ){ return function(){ let res = fn.apply( this, arguments ); afterfn.apply( this, arguments ); return res; } } // 裝飾函數(shù) var addFilter=function(){ console.log("加濾鏡"); } // 用裝飾函數(shù)裝飾原函數(shù) takePhoto=after(takePhoto,addFilter); takePhoto();
這樣我們就實(shí)現(xiàn)了抽離拍照與濾鏡邏輯,如果以后需要自動上傳功能,也可以通過aop函數(shù)after來添加。
ES5 下裝飾者的實(shí)現(xiàn)在 ES5 中引入了Object.defineProperty,我們可以更方便的給對象添加屬性:
let takePhoto = function () { console.log("拍照片"); } // 給 takePhoto 添加屬性 after Object.defineProperty(takePhoto, "after", { writable: true, value: function () { console.log("加濾鏡"); }, }); // 給 takePhoto 添加屬性 before Object.defineProperty(takePhoto, "before", { writable: true, value: function () { console.log("打開相機(jī)"); }, }); // 包裝方法 let aop = function (fn) { return function () { fn.before() fn() fn.after() } } takePhoto = aop(takePhoto) takePhoto()基于原型鏈和類的裝飾者實(shí)現(xiàn)
我們知道,在 JavaScript 中,函數(shù)也好,類也好都有著自己的原型,通過原型鏈我們也能夠很方便的動態(tài)擴(kuò)展,以下是基于原型鏈的寫法:
class Test { takePhoto() { console.log("拍照"); } } // after AOP function after(target, action, fn) { let old = target.prototype[action]; if (old) { target.prototype[action] = function () { let self = this; fn.bind(self); fn(handle); } } } // 用 AOP 函數(shù)修飾原函數(shù) after(Test, "takePhoto", () => { console.log("添加濾鏡"); }); let t = new Test(); t.takePhoto();使用 ES7 修飾器實(shí)現(xiàn)裝飾者
在 ES7 中引入了@decorator 修飾器的提案,參考阮一峰的文章)。修飾器是一個函數(shù),用來修改類的行為。目前Babel轉(zhuǎn)碼器已經(jīng)支持。注意修飾器只能裝飾類或者類屬性、方法。三者的具體區(qū)別請參考 MDN Object.defineProperty ;而 TypeScript 的實(shí)現(xiàn)又有所不同:TypeScript Decorator。
接下來我們通過修飾器來實(shí)現(xiàn)對方法的裝飾:
function after(target, key, desc) { const { value } = desc; desc.value = function (...args) { let res = value.apply(this, args); console.log("加濾鏡") return res; } return desc; } class Test{ @after takePhoto(){ console.log("拍照") } } let t = new Test() t.takePhoto()
可以看到,使用修飾器的代碼非常簡潔明了。
場景:性能上報裝飾者模式可以應(yīng)用在很多場景,典型的場景是記錄某異步請求請求耗時的性能數(shù)據(jù)并上報:
function report(target, key, desc) { const { value } = desc; desc.value = async function (...args) { let start = Date.now(); let res = await value.apply(this, args); let millis = Date.now()-start; // 上報代碼 return res; } return desc; } class Test{ @report getData(url){ // fetch 代碼 } } let t = new Test() t.getData()
這樣使用@report修飾后的代碼就會上報請求所消耗的時間。擴(kuò)展或者修改report函數(shù)不會影響業(yè)務(wù)代碼,反之亦然。
場景:異常處理我們可以對原有代碼進(jìn)行簡單的異常處理,而無需侵入式的修改:
function handleError(target, key, desc) { const { value } = desc; desc.value = async function (...args) { let res; try{ res = await value.apply(this, args); }catch(err){ // 異常處理 logger.error(err) } return res; } return desc; } class Test{ @handleError getData(url){ // fetch 代碼 } } let t = new Test() t.getData()
通過以上兩個示例我們可以看到,修飾器的定義很簡單,功能卻非常強(qiáng)大。
小結(jié)我們一步一步通過高階函數(shù)、原型鏈、Object.defineProperty和@Decorator分別實(shí)現(xiàn)了裝飾者模式。接下來在回顧一下:
裝飾者模式非常適合給業(yè)務(wù)代碼附加非業(yè)務(wù)相關(guān)功能(如日志上報),就如同給照片加濾鏡;
裝飾者模式非常適合無痛擴(kuò)展別人的代碼(你經(jīng)常需要接手別人的項(xiàng)目吧)
有些朋友可能會覺得裝飾者模式和 vue 的 mixin 機(jī)制很像,其實(shí)他們都是“開放-封閉原則”和“單一職責(zé)原則”的體現(xiàn)。
附上代碼 jsbin 地址:
ES3
ES5
原型鏈
ES7
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/104942.html
摘要:裝飾者模式定義裝飾者模式能夠在不改變對象自身的基礎(chǔ)上,在程序運(yùn)行期間給對像動態(tài)的添加職責(zé)。與繼承相比,裝飾者是一種更輕便靈活的做法。 裝飾者模式 定義 : 裝飾者(decorator)模式能夠在不改變對象自身的基礎(chǔ)上,在程序運(yùn)行期間給對像動態(tài)的添加職責(zé)。與繼承相比,裝飾者是一種更輕便靈活的做法。 在不改變對象自身的基礎(chǔ)上,在程序運(yùn)行期間給對象動態(tài)地添加一些額外職責(zé) 特點(diǎn) : 可以動態(tài)的...
摘要:用戶名不能為空密碼不能為空校驗(yàn)未通過使用優(yōu)化代碼返回的情況直接,不再執(zhí)行后面的原函數(shù)用戶名不能為空密碼不能為空 本文是《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》的學(xué)習(xí)筆記,例子來源于書中,對于設(shè)計(jì)模式的看法,推薦看看本書作者的建議。 什么是裝飾者模式? 給對象動態(tài)增加職責(zé)的方式成為裝飾者模式。 裝飾者模式能夠在不改變對象自身的基礎(chǔ)上,在運(yùn)行程序期間給對象動態(tài)地添加職責(zé)。這是一種輕便靈活...
摘要:會一直完善下去,歡迎建議和指導(dǎo),同時也歡迎中用到了那些設(shè)計(jì)模式中用到了那些設(shè)計(jì)模式這兩個問題,在面試中比較常見。工廠設(shè)計(jì)模式使用工廠模式可以通過或創(chuàng)建對象。 我自己總結(jié)的Java學(xué)習(xí)的系統(tǒng)知識點(diǎn)以及面試問題,已經(jīng)開源,目前已經(jīng) 41k+ Star。會一直完善下去,歡迎建議和指導(dǎo),同時也歡迎Star: https://github.com/Snailclimb... JDK 中用到了那...
摘要:但是,這樣做的后果就是,我們會不斷的改變本體,就像把鳳姐送去做整形手術(shù)一樣。在中,我們叫做引用裝飾。所以,這里引入的裝飾模式裝飾親切,熟悉,完美。實(shí)例講解裝飾上面那個例子,只能算是裝飾模式的一個不起眼的角落。 裝飾者,英文名叫decorator. 所謂的裝飾,從字面可以很容易的理解出,就是給 土肥圓,化個妝,華麗的轉(zhuǎn)身為白富美,但本體還是土肥圓。 說人話.咳咳~ 在js里面一切都是對...
摘要:設(shè)計(jì)模式裝飾者模式何為裝飾者,意思就是,在不影響對象主功能的情況下,再添加一些額外的功能,使對象具備更多的功能。與繼承相比,裝飾者是一種更靈活輕便的做法。 javascript設(shè)計(jì)模式 --- 裝飾者模式 何為裝飾者,意思就是,在不影響對象主功能的情況下,再添加一些額外的功能,使對象具備更多的功能。與繼承相比,裝飾者是一種更靈活輕便的做法。下面我們看看javascript里裝飾者模式 ...
閱讀 2265·2023-04-26 02:14
閱讀 2935·2021-09-30 09:46
閱讀 2108·2021-09-24 09:48
閱讀 967·2021-09-24 09:47
閱讀 3258·2019-08-30 15:44
閱讀 1885·2019-08-30 15:44
閱讀 3289·2019-08-30 14:18
閱讀 1958·2019-08-30 12:58