摘要:什么是裝飾器模式向一個現(xiàn)有的對象添加新的功能,同時又不改變其結(jié)構(gòu)的設(shè)計模式被稱為裝飾器模式,它是作為現(xiàn)有的類的一個包裝。中的裝飾器模式中有一個的提案,使用一個以開頭的函數(shù)對中的及其屬性方法進(jìn)行修飾。
1 什么是裝飾器模式
向一個現(xiàn)有的對象添加新的功能,同時又不改變其結(jié)構(gòu)的設(shè)計模式被稱為裝飾器模式(Decorator Pattern),它是作為現(xiàn)有的類的一個包裝(Wrapper)。
可以將裝飾器理解為游戲人物購買的裝備,例如LOL中的英雄剛開始游戲時只有基礎(chǔ)的攻擊力和法強。但是在購買的裝備后,在觸發(fā)攻擊和技能時,能夠享受到裝備帶來的輸出加成。我們可以理解為購買的裝備給英雄的攻擊和技能的相關(guān)方法進(jìn)行了裝飾。
這里推薦一篇淘寶前端團隊的博文,很有趣的以鋼鐵俠的例子來講解了裝飾者模式。
2 ESnext中的裝飾器模式ESnext中有一個Decorator的提案,使用一個以 @ 開頭的函數(shù)對ES6中的class及其屬性、方法進(jìn)行修飾。Decorator的詳細(xì)語法請參考阮一峰的《ECMASciprt入門 —— Decorator》。
目前Decorator的語法還只是一個提案,如果期望現(xiàn)在使用裝飾器模式,需要安裝配合babel + webpack并結(jié)合插件實現(xiàn)。
npm安裝依賴
npm install babel-core babel-loader babel-plugin-transform-decorators babel-plugin-transform-decorators-legacy babel-preset-env
配置.babelrc文件
{ "presets": ["env"], "plugins": ["transform-decorators-legacy"] }
在webpack.config.js中添加babel-loader
module: { rules: [ { test: /.js$/, exclude: /node_modules/, loader: "babel-loader" } ], }
如果你使用的IDE為Visual Studio Code,可能還需要在項目根目錄下添加以下tsconfig.json文件來組織一個ts檢查的報錯。
{ "compilerOptions": { "experimentalDecorators": true, "allowJs": true, "lib": [ "es6" ], } }
下面我將實現(xiàn)3個裝飾器,分別為@autobind、@debounce、@deprecate。
2.1 @autobind實現(xiàn)this指向原對象在JavaScript中,this的指向問題一直是一個老生常談的話題,在Vue或React這類框架的使用過程中,新手很有可能一不小心就丟失了this的指向?qū)е路椒ㄕ{(diào)用錯誤。例如下面一段代碼:
class Person { getPerson() { return this; } } let person = new Person(); let { getPerson } = person; console.log(getPerson() === person); // false
上面的代碼中, getPerson方法中的this默認(rèn)指向Person類的實例,但是如果將Person通過解構(gòu)賦值的方式提取出來,那么此時的this指向為undefined。所以最終的打印結(jié)果為false。
此時我們可以實現(xiàn)一個autobind的函數(shù),用來裝飾getPerson這個方法,實現(xiàn)this永遠(yuǎn)指向Person的實例。
function autobind(target, key, descriptor) { var fn = descriptor.value; var configurable = descriptor.configurable; var enumerable = descriptor.enumerable; // 返回descriptor return { configurable: configurable, enumerable: enumerable, get: function get() { // 將該方法綁定this var boundFn = fn.bind(this); // 使用Object.defineProperty重新定義該方法 Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: false, value: boundFn }) return boundFn; } } }
我們通過bind實現(xiàn)了this的綁定,并在get中利用Object.defineProperty重寫了該方法,將value定義為通過bind綁定后的函數(shù)boundFn,以此實現(xiàn)了this永遠(yuǎn)指向?qū)嵗O旅嫖覀優(yōu)?b>getPerson方法加上裝飾并調(diào)用。
class Person { @autobind getPerson() { return this; } } let person = new Person(); let { getPerson } = person; console.log(getPerson() === person); // true2.2 @debounce實現(xiàn)函數(shù)防抖
函數(shù)防抖(debounce)在前端項目中有著很多的應(yīng)用,例如在resize或scroll等事件中操作DOM,或?qū)τ脩糨斎雽崿F(xiàn)實時ajax搜索等會被高頻的觸發(fā),前者會對瀏覽器性能產(chǎn)生直觀的影響,后者會對服務(wù)器產(chǎn)生較大的壓力,我們期望這類高頻連續(xù)觸發(fā)的事件在觸發(fā)結(jié)束后再做出響應(yīng),這就是函數(shù)防抖的應(yīng)用。
class Editor { constructor() { this.content = ""; } updateContent(content) { console.log(content); this.content = content; // 后面有一些消耗性能的操作 } } const editor1 = new Editor(); editor1.updateContent(1); setTimeout(() => { editor1.updateContent(2); }, 400); const editor2= new Editor(); editor2.updateContent(3); setTimeout(() => { editor2.updateContent(4); }, 600); // 打印結(jié)果: 1 3 2 4
上面的代碼中我們定義了Editor這個類,其中updateContent方法會在用戶輸入時執(zhí)行并可能有一些消耗性能的DOM操作,這里我們在該方法內(nèi)部打印了傳入的參數(shù)以驗證調(diào)用過程。可以看到4次的調(diào)用結(jié)果分別為1 3 2 4。
下面我們實現(xiàn)一個debounce函數(shù),該方法傳入一個數(shù)字類型的timeout參數(shù)。
function debounce(timeout) { const instanceMap = new Map(); // 創(chuàng)建一個Map的數(shù)據(jù)結(jié)構(gòu),將實例化對象作為key return function (target, key, descriptor) { return Object.assign({}, descriptor, { value: function value() { // 清除延時器 clearTimeout(instanceMap.get(this)); // 設(shè)置延時器 instanceMap.set(this, setTimeout(() => { // 調(diào)用該方法 descriptor.value.apply(this, arguments); // 將延時器設(shè)置為 null instanceMap.set(this, null); }, timeout)); } }) } }
上面的方法中,我們采用了ES6提供的Map數(shù)據(jù)結(jié)構(gòu)去實現(xiàn)實例化對象和延時器的映射。在函數(shù)的內(nèi)部,首先清除延時器,接著設(shè)置延時執(zhí)行函數(shù),這是實現(xiàn)debounce的通用方法,下面我們來測試一下debounce裝飾器。
class Editor { constructor() { this.content = ""; } @debounce(500) updateContent(content) { console.log(content); this.content = content; } } const editor1 = new Editor(); editor1.updateContent(1); setTimeout(() => { editor1.updateContent(2); }, 400); const editor2= new Editor(); editor2.updateContent(3); setTimeout(() => { editor2.updateContent(4); }, 600); //打印結(jié)果: 3 2 4
上面調(diào)用了4次updateContent方法,打印結(jié)果為3 2 4。1由于在400ms內(nèi)被重復(fù)調(diào)用而沒有被打印,這符合我們的參數(shù)為500的預(yù)期。
2.3 @deprecate實現(xiàn)警告提示在使用第三方庫的過程中,我們會時不時的在控制臺遇見一些警告,這些警告用來提醒開發(fā)者所調(diào)用的方法會在下個版本中被棄用。這樣的一行打印信息也許我們的常規(guī)做法是在方法內(nèi)部添加一行代碼即可,這樣其實在源碼閱讀上并不友好,也不符合單一職責(zé)原則。如果在需要拋出警告的方法前面加一個@deprecate的裝飾器來實現(xiàn)警告,會友好得多。
下面我們來實現(xiàn)一個@deprecate的裝飾器,其實這類的裝飾器也可以擴展成為打印日志裝飾器@log,上報信息裝飾器@fetchInfo等。
function deprecate(deprecatedObj) { return function(target, key, descriptor) { const deprecatedInfo = deprecatedObj.info; const deprecatedUrl = deprecatedObj.url; // 警告信息 const txt = `DEPRECATION ${target.constructor.name}#${key}: ${deprecatedInfo}. ${deprecatedUrl ? "See "+ deprecatedUrl + " for more detail" : ""}`; return Object.assign({}, descriptor, { value: function value() { // 打印警告信息 console.warn(txt); descriptor.value.apply(this, arguments); } }) } }
上面的deprecate函數(shù)接受一個對象參數(shù),該參數(shù)分別有info和url兩個鍵值,其中info填入警告信息,url為選填的詳情網(wǎng)頁地址。下面我們來為一個名為MyLib的庫的deprecatedMethod方法添加該裝飾器吧!
class MyLib { @deprecate({ info: "The methods will be deprecated in next version", url: "http://www.baidu.com" }) deprecatedMethod(txt) { console.log(txt) } } const lib = new MyLib(); lib.deprecatedMethod("調(diào)用了一個要在下個版本被移除的方法"); // DEPRECATION MyLib#deprecatedMethod: The methods will be deprecated in next version. See http://www.baidu.com for more detail // 調(diào)用了一個要在下個版本被移除的方法3 總結(jié)
通過ESnext中的裝飾器實現(xiàn)裝飾器模式,不僅有為類擴充功能的作用,而且在閱讀源碼的過程中起到了提示作用。上面所舉到的例子只是結(jié)合裝飾器的新語法和裝飾器模式做了一個簡單封裝,請勿用于生產(chǎn)環(huán)境。如果你現(xiàn)在已經(jīng)體會到了裝飾器模式的好處,并想在項目中大量使用,不妨看一下core-decorators這個庫,其中封裝了很多常用的裝飾器.
參考文獻(xiàn)IMWeb的前端博客:淺談JS中的裝飾器模式
淘寶前端團隊:ES7 Decorator 裝飾者模式
阮一峰:ECMAScript 6 入門
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/96841.html
摘要:裝飾者模式參與者裝飾者和被裝飾者共同的父類,是一個接口或者抽象類,用來定義基本行為定義具體對象,即被裝飾者抽象裝飾者,繼承自,從外類來擴展。三裝飾器高階組件可以看做是裝飾器模式在的實現(xiàn)。 一 裝飾者模式 優(yōu)先使用對象組合而不是類繼承。 --《設(shè)計模式》 1.什么是裝飾者模式 定義:動態(tài)的給對象添加一些額外的屬性或行為。相比于使用繼承,裝飾者模式更加靈活。 2.裝飾者模式參與者 Co...
摘要:設(shè)計模式的定義在面向?qū)ο筌浖O(shè)計過程中針對特定問題的簡潔而優(yōu)雅的解決方案。從前由于使用的局限性,和做的應(yīng)用相對簡單,不被重視,就更談不上設(shè)計模式的問題。 ‘從大處著眼,從小處著手’,以前對這句話一知半解,自從踏出校門走入社會,開始工作以來,有了越來越深的理解,偶有發(fā)現(xiàn)這句話用在程序開發(fā)中也有用,所以,近段時間開始嘗試著分析jQuery源碼,分析angularjs源碼,學(xué)習(xí)設(shè)計模式。 設(shè)...
摘要:作者按每天一個設(shè)計模式旨在初步領(lǐng)會設(shè)計模式的精髓,目前采用和兩種語言實現(xiàn)。誠然,每種設(shè)計模式都有多種實現(xiàn)方式,但此小冊只記錄最直截了當(dāng)?shù)膶崿F(xiàn)方式原文地址是每天一個設(shè)計模式之裝飾者模式歡迎關(guān)注個人技術(shù)博客。 作者按:《每天一個設(shè)計模式》旨在初步領(lǐng)會設(shè)計模式的精髓,目前采用javascript和python兩種語言實現(xiàn)。誠然,每種設(shè)計模式都有多種實現(xiàn)方式,但此小冊只記錄最直截了當(dāng)?shù)膶崿F(xiàn)方式...
摘要:作者按每天一個設(shè)計模式旨在初步領(lǐng)會設(shè)計模式的精髓,目前采用和兩種語言實現(xiàn)。誠然,每種設(shè)計模式都有多種實現(xiàn)方式,但此小冊只記錄最直截了當(dāng)?shù)膶崿F(xiàn)方式原文地址是每天一個設(shè)計模式之裝飾者模式歡迎關(guān)注個人技術(shù)博客。 作者按:《每天一個設(shè)計模式》旨在初步領(lǐng)會設(shè)計模式的精髓,目前采用javascript和python兩種語言實現(xiàn)。誠然,每種設(shè)計模式都有多種實現(xiàn)方式,但此小冊只記錄最直截了當(dāng)?shù)膶崿F(xiàn)方式...
閱讀 1209·2021-11-22 12:05
閱讀 1336·2021-09-29 09:35
閱讀 630·2019-08-30 15:55
閱讀 3122·2019-08-30 14:12
閱讀 954·2019-08-30 14:11
閱讀 2874·2019-08-30 13:10
閱讀 2400·2019-08-29 16:33
閱讀 3326·2019-08-29 11:02