新手快速入門(mén)JavaScript裝飾者模式與AOP
什么是裝飾者模式
當(dāng)我們拍了一張照片準(zhǔn)備發(fā)朋友圈時(shí),許多小伙伴會(huì)選擇給照片加上濾鏡。同一張照片、不同的濾鏡組合起來(lái)就會(huì)有不同的體驗(yàn)。這里實(shí)際上就應(yīng)用了裝飾者模式:是通過(guò)濾鏡裝飾了照片。在不改變對(duì)象(照片)的情況下動(dòng)態(tài)的為其添加功能(濾鏡)。
需要注意的是:由于 JavaScript 語(yǔ)言動(dòng)態(tài)的特性,我們很容易就能改變某個(gè)對(duì)象(JavaScript 中函數(shù)是一等公民)。但是我們要盡量避免直接改寫(xiě)某個(gè)函數(shù),這會(huì)導(dǎo)致代碼的可維護(hù)性、可擴(kuò)展性變差,甚至?xí)廴酒渌麡I(yè)務(wù)。
什么是 AOP
想必大家對(duì)"餐前洗手、飯后漱口"都不陌生。這句標(biāo)語(yǔ)其實(shí)就是 AOP 在生活中的例子:吃飯這個(gè)動(dòng)作相當(dāng)于切點(diǎn),我們可以在這個(gè)切點(diǎn)前、后插入其它如洗手等動(dòng)作。
AOP(Aspect-Oriented Programming):面向切面編程,是對(duì) OOP 的補(bǔ)充。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,也可以隔離業(yè)務(wù)無(wú)關(guān)的功能比如日志上報(bào)、異常處理等,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高業(yè)務(wù)無(wú)關(guān)的功能的復(fù)用性,也就提高了開(kāi)發(fā)的效率。
在 JavaScript 中,我們可以通過(guò)裝飾者模式來(lái)實(shí)現(xiàn) AOP,但是兩者并不是一個(gè)維度的概念。 AOP 是一種編程范式,而裝飾者是一種設(shè)計(jì)模式。
ES3 下裝飾者的實(shí)現(xiàn)
了解了裝飾者模式和 AOP 的概念之后,我們寫(xiě)一段能夠兼容 ES3 的代碼來(lái)實(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)了抽離拍照與濾鏡邏輯,如果以后需要自動(dòng)上傳功能,也可以通過(guò)aop函數(shù)after來(lái)添加。
ES5 下裝飾者的實(shí)現(xiàn)
在 ES5 中引入了Object.defineProperty,我們可以更方便的給對(duì)象添加屬性:
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('打開(kāi)相機(jī)');
},
});
// 包裝方法
let aop = function (fn) {
return function () {
fn.before()
fn()
fn.after()
}
}
takePhoto = aop(takePhoto)
takePhoto()
基于原型鏈和類的裝飾者實(shí)現(xiàn)
我們知道,在 JavaScript 中,函數(shù)也好,類也好都有著自己的原型,通過(guò)原型鏈我們也能夠很方便的動(dòng)態(tài)擴(kuò)展,以下是基于原型鏈的寫(xiě)法:
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 修飾器的提案,參考阮一峰的文章。修飾器是一個(gè)函數(shù),用來(lái)修改類的行為。目前Babel轉(zhuǎn)碼器已經(jīng)支持。注意修飾器只能裝飾類或者類屬性、方法。三者的具體區(qū)別請(qǐng)參考 MDN Object.defineProperty ;而 TypeScript 的實(shí)現(xiàn)又有所不同:TypeScript Decorator。
接下來(lái)我們通過(guò)修飾器來(lái)實(shí)現(xiàn)對(duì)方法的裝飾:
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()
可以看到,使用修飾器的代碼非常簡(jiǎn)潔明了。
場(chǎng)景:性能上報(bào)
裝飾者模式可以應(yīng)用在很多場(chǎng)景,典型的場(chǎng)景是記錄某異步請(qǐng)求請(qǐng)求耗時(shí)的性能數(shù)據(jù)并上報(bào):
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;
// 上報(bào)代碼
return res;
}
return desc;
}
class Test{
@report
getData(url){
// fetch 代碼
}
}
let t = new Test()
t.getData()
這樣使用@report修飾后的代碼就會(huì)上報(bào)請(qǐng)求所消耗的時(shí)間。擴(kuò)展或者修改report函數(shù)不會(huì)影響業(yè)務(wù)代碼,反之亦然。
場(chǎng)景:異常處理
我們可以對(duì)原有代碼進(jìn)行簡(jiǎn)單的異常處理,而無(wú)需侵入式的修改:
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()
通過(guò)以上兩個(gè)示例我們可以看到,修飾器的定義很簡(jiǎn)單,功能卻非常強(qiáng)大。
小結(jié)
我們一步一步通過(guò)高階函數(shù)、原型鏈、Object.defineProperty和@Decorator分別實(shí)現(xiàn)了裝飾者模式。接下來(lái)在回顧一下:
- 裝飾者模式非常適合給業(yè)務(wù)代碼附加非業(yè)務(wù)相關(guān)功能(如日志上報(bào)),就如同給照片加濾鏡;
- 裝飾者模式非常適合無(wú)痛擴(kuò)展別人的代碼(你經(jīng)常需要接手別人的項(xiàng)目吧)
有些朋友可能會(huì)覺(jué)得裝飾者模式和 vue 的 mixin 機(jī)制很像,其實(shí)他們都是“開(kāi)放-封閉原則”和“單一職責(zé)原則”的體現(xiàn)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,
相關(guān)文章
javascript中slice(),splice(),split(),substring(),substr()使用方法
這篇文章主要介紹了javascript中slice(),splice(),split(),substring(),substr()使用方法,需要的朋友可以參考下2015-03-03
Javascript 多瀏覽器兼容總結(jié)(實(shí)戰(zhàn)經(jīng)驗(yàn))
多瀏覽器兼容一直都是前端備受關(guān)注的問(wèn)題,本文整理了一些實(shí)戰(zhàn)的經(jīng)驗(yàn),個(gè)人感覺(jué)還不錯(cuò),需要的朋友可以參考下2013-10-10
JS設(shè)計(jì)模式之觀察者模式實(shí)現(xiàn)實(shí)時(shí)改變頁(yè)面中金額數(shù)的方法
這篇文章主要介紹了JS設(shè)計(jì)模式之觀察者模式實(shí)現(xiàn)實(shí)時(shí)改變頁(yè)面中金額數(shù)的方法,結(jié)合實(shí)例形式對(duì)比分析了javascript基于觀察者模式實(shí)時(shí)改變頁(yè)面金額數(shù)的相關(guān)操作技巧,需要的朋友可以參考下2018-02-02
淺談DOM的操作以及性能優(yōu)化問(wèn)題-重繪重排
下面小編就為大家?guī)?lái)一篇淺談DOM的操作以及性能優(yōu)化問(wèn)題-重繪重排。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01
JavaScript document 對(duì)象常用方法
本文詳細(xì)介紹了JavaScript中的document對(duì)象,它是連接JavaScript與網(wǎng)頁(yè)內(nèi)容的橋梁,document對(duì)象提供了訪問(wèn)和修改網(wǎng)頁(yè)結(jié)構(gòu)、內(nèi)容和樣式的接口,支持各種屬性和方法,適用于動(dòng)態(tài)更新內(nèi)容、表單驗(yàn)證和動(dòng)態(tài)樣式切換等場(chǎng)景,感興趣的朋友跟隨小編一起看看吧2025-02-02
通過(guò)繼承IHttpHandle實(shí)現(xiàn)JS插件的組織與管理
最近,項(xiàng)目中的用到的Js插件越來(lái)越多,有的是用原生javascript寫(xiě)的,有的是調(diào)用的jquery插件,頁(yè)面上Js和Css文件的引用也越來(lái)越混亂,而且Js文件之間還有引用先后的依賴關(guān)系2010-07-07

