JS裝飾器的一次最佳實(shí)踐

緣起

近期產(chǎn)品提了個類似支付寶收能量活動的需求,其中有一項(xiàng)需求是獲得能量球需要完成一些任務(wù), 比如:分享活動頁面可以獲得a能量,完成場景開啟可以獲得b能量,操作遙控器可以獲得c能量等等……

這些任務(wù)都是產(chǎn)品已經(jīng)存在的功能策橘,如果更改每一項(xiàng)產(chǎn)生能量球任務(wù)的功能炸渡,則可能需要不斷的 repeat 自己,增大代碼量的同時丽已,后期如果變更需求或者產(chǎn)生bug也需要重復(fù)的體力勞動蚌堵。 這些都是我不期望看到的。那么有沒有一種優(yōu)雅的解決方案呢?

其實(shí)能量球任務(wù) 和 產(chǎn)品已存在的功能是沒有必然關(guān)系的吼畏,只是因?yàn)檫@個活動才有了那么一絲絲關(guān)聯(lián)督赤。 所以沒有必要耦合在一起,恰好近期在看設(shè)計(jì)模式泻蚊,裝飾器模式給了我一些靈感:

裝飾器模式(Decorator Pattern)允許向一個現(xiàn)有的對象添加新的功能躲舌,同時又不改變其結(jié)構(gòu)。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式性雄,它是作為現(xiàn)有的類的一個包裝没卸。
這種模式創(chuàng)建了一個裝飾類,用來包裝原有的類秒旋,并在保持類方法簽名完整性的前提下约计,提供了額外的功能。

何不寫一個能量球任務(wù)的裝飾器迁筛,裝飾一下需要完成任務(wù)的功能方法煤蚌,這樣既減少了代碼量、又不會repeat自己细卧,是一種比較優(yōu)雅的解決方案尉桩。

性空

既然使用裝飾器模式,那么必須先寫出一個能量球任務(wù)裝飾器來酒甸。

V1版本如下所示:

// 收能量任務(wù)裝飾器
function getPowerTask (taskCode) {
  return function (target, property, descriptor) {
    const rawFunc = descriptor.value;
    descriptor.value = async (...args) => {
      console.log(target, `property=${property}`, descriptor)
      await powerModel.finishTask(taskCode);
      await rawFunc.apply(target, args);
    }
    return descriptor;
  }
}

一個活動頁面分享成功后調(diào)用能量球任務(wù)裝飾器的代碼如下所示:

export default class ShareTask extends Component {
  ……
  shareView() {
    const shareMod = this.getShareMod();
    const shareOpt = this.getShareOpts();
    shareMod.share(shareOpt).then(
        res => this.shareAfter(),
        err => console.log(err)
     )
  }

  @getPowerTask('share')
  async shareAfter() {
      console.log('shareAfter');
      await this.props.shareAfter();
  }
  ……

  render() {
    ……
  }
}

結(jié)果是喜憂參半魄健, 喜的是可以成功的觸發(fā)收能量任務(wù),憂的是shareAfter方法內(nèi) 獲取this.props 的結(jié)果是 undefined插勤。

為啥是這樣的結(jié)果呢沽瘦,后來打斷點(diǎn)調(diào)試發(fā)現(xiàn) target 是 Component 原型, 并不是 Component 實(shí)例农尖。 后來翻閱資料發(fā)現(xiàn)其實(shí) Decorator 只是ES7的語法糖析恋,類方法上的裝飾器底層還是基于 Object.defineProperty 來實(shí)現(xiàn)的, 上面的裝飾器使用babel翻譯成ES5其實(shí)就是如下執(zhí)行的:

getPowerTask(ShareTask.prototype, 'shareAfter', descriptor);
// 類似于
Object.defineProperty(ShareTask.prototype, 'shareAfter', descriptor);

把 shareTask.proptype 的 shareAfter 方法重新定義成了我們的方法盛卡。 既然裝飾器只是一層封裝助隧,那么當(dāng)方法具體執(zhí)行的時候,是不是就是在 實(shí)例里面了滑沧, 考慮到這樣并村,我對裝飾器進(jìn)行了以下改裝:

1、箭頭方法的this指向是在方法定義的地方滓技,這里this需要指向執(zhí)行的地方哩牍,所以改成普通方法
2、rawFunc.apply 的上下文改成this令漂,即棄用原型對象膝昆,改成執(zhí)行上下文對象

V2收能量裝飾器

function getPowerTask (taskCode) {
  return function (target, property, descriptor) {
    const rawFunc = descriptor.value;
    descriptor.value = async function(...args) => {
      console.log(target, `property=${property}`, descriptor)
      await powerModel.finishTask(taskCode);
      await rawFunc.apply(this, args);
    }
    return descriptor;
  }
}

后來試了一下V2 版本丸边, 果然成功的獲取到了 props 屬性。

總結(jié)

此次成功的實(shí)踐了裝飾器模式荚孵,雖說軟件工程沒有銀彈妹窖, 這次開發(fā)也算是一次尋找“鉛彈”的過程吧,下面總結(jié)一些裝飾器填坑的經(jīng)驗(yàn):

  • this指向問題

Object.defineProperty方法是對類原型的方法進(jìn)行重新定義收叶,執(zhí)行的時候還是在類實(shí)例下執(zhí)行骄呼。如果想封裝類的原有方法同時想同時使用類的實(shí)例,descriptor.value 必須定義一個普通函數(shù)滔驾,不能使用箭頭函數(shù)谒麦。實(shí)例執(zhí)行的時候this的指向就是當(dāng)前實(shí)例類

  • 普通函數(shù)不能使用裝飾器

因?yàn)閖avascript在生成上下文對象的時候存在函數(shù)聲明提升,所以對普通函數(shù)裝飾失效哆致。

  • 不要手里有錘子绕德,全世界去找釘子

他山之石

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市摊阀,隨后出現(xiàn)的幾起案子耻蛇,更是在濱河造成了極大的恐慌,老刑警劉巖胞此,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臣咖,死亡現(xiàn)場離奇詭異,居然都是意外死亡漱牵,警方通過查閱死者的電腦和手機(jī)夺蛇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酣胀,“玉大人刁赦,你說我怎么就攤上這事∥畔猓” “怎么了甚脉?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铆农。 經(jīng)常有香客問我牺氨,道長,這世上最難降的妖魔是什么墩剖? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任猴凹,我火速辦了婚禮,結(jié)果婚禮上岭皂,老公的妹妹穿的比我還像新娘郊霎。我一直安慰自己,他們只是感情好蒲障,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般揉阎。 火紅的嫁衣襯著肌膚如雪庄撮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天毙籽,我揣著相機(jī)與錄音洞斯,去河邊找鬼。 笑死坑赡,一個胖子當(dāng)著我的面吹牛烙如,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毅否,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼亚铁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了螟加?” 一聲冷哼從身側(cè)響起徘溢,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捆探,沒想到半個月后然爆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡黍图,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年曾雕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片助被。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡剖张,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恰起,到底是詐尸還是另有隱情修械,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布检盼,位于F島的核電站肯污,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吨枉。R本人自食惡果不足惜蹦渣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望貌亭。 院中可真熱鬧柬唯,春花似錦、人聲如沸圃庭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拘央,卻和暖如春涂屁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背灰伟。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工拆又, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人栏账。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓帖族,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挡爵。 傳聞我的和親對象是個殘疾皇子竖般,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354