日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
一文讀懂JS裝飾器,這是一個(gè)會(huì)打扮的裝飾器

俗話說,人靠衣裝,佛靠金裝。大街上的小姐姐都喜歡把自己打扮得美美的,讓你忍不住多看幾眼,這就是裝飾的作用。

目前成都創(chuàng)新互聯(lián)公司已為1000多家的企業(yè)提供了網(wǎng)站建設(shè)、域名、雅安服務(wù)器托管網(wǎng)站運(yùn)營、企業(yè)網(wǎng)站設(shè)計(jì)、保定網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

1. 前言

裝飾器是最新的 ECMA 中的一個(gè)提案,是一種與類(class)相關(guān)的語法,用來注釋或修改類和類方法。裝飾器在 Python 和 Java 等語言中也被大量使用。裝飾器是實(shí)現(xiàn) AOP(面向切面)編程的一種重要方式。

下面是一個(gè)使用裝飾器的簡單例子,這個(gè) @readonly 可以將 count 屬性設(shè)置為只讀??梢钥闯鰜?,裝飾器大大提高了代碼的簡潔性和可讀性。

 
 
 
 
  1. class Person { 
  2.     @readonly count = 0; 
  3. }

由于瀏覽器還未支持裝飾器,為了讓大家能夠正??吹叫Ч?,這里我使用 Parcel 進(jìn)行了一下簡單的配置,可以去 clone 這個(gè)倉庫后再來運(yùn)行本文涉及到的所有例子,倉庫地址:[learn es6][https://github.com/yinguangyao/ES6]

本文涉及到 [Object.defineProperty][https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty]、高階函數(shù)等知識(shí),如果之前沒有了解過相關(guān)概念,建議先了解后再來閱讀本文。

2. 裝飾器模式

在開始講解裝飾器之前,先從經(jīng)典的裝飾器模式說起。裝飾器模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu),是作為對(duì)現(xiàn)有類的一個(gè)包裝。一般來說,在代碼設(shè)計(jì)中,我們應(yīng)當(dāng)遵循「多用組合,少用繼承」的原則。通過裝飾器模式動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來說,裝飾器模式相比生成子類更為靈活。

2.1 一個(gè)英雄聯(lián)盟的例子

下班回去和朋友愉快地開黑,當(dāng)我正在用亞索「面對(duì)疾風(fēng)吧」的時(shí)候,突然想到,如果讓我設(shè)計(jì)亞索英雄,我該怎么實(shí)現(xiàn)呢?

我想了想,肯定會(huì)先設(shè)計(jì)一個(gè)英雄的類。

 
 
 
 
  1. class Hero { 
  2.     attack() {} 
  3. }

然后,再實(shí)現(xiàn)一個(gè) Yasuo 的類來繼承這個(gè) Hero 類。

 
 
 
 
  1. class Yasuo extends Hero { 
  2.     attack() { 
  3.         console.log("斬鋼閃"); 
  4.     } 
  5. }

我還在想這個(gè)問題的時(shí)候,隊(duì)友已經(jīng)打了大龍,我的亞索身上就出現(xiàn)了大龍 buff 的印記。我突然想到,那該怎么給英雄增加大龍 buff 呢?那增加個(gè)大龍 buff 的屬性不行嗎?當(dāng)然不太行,要知道,英雄聯(lián)盟里面的大龍 buff 是會(huì)增加收益的。嗯,聰明的我已經(jīng)想到辦法了,再繼承一次不就好了嗎?

 
 
 
 
  1. class BaronYasuo extends Yasuo { 
  2. }

厲害了,但是如果亞索身上還有其他 buff 呢?畢竟 LOL 里面是有紅 buff、藍(lán) buff、大龍 buff等等存在,那豈不是有多少種就要增加多少個(gè)類嗎?

可以換種思路來思考這個(gè)問題,如果把 buff 當(dāng)做我們身上的衣服。在不同的季節(jié),我們會(huì)換上不同的衣服,到了冬天,甚至還會(huì)疊加多件衣服。當(dāng) buff 消失了,就相當(dāng)于把這件衣服脫了下來。如下圖所示:

衣服對(duì)人來說起到裝飾的作用,buff 對(duì)于亞索來說也只是增強(qiáng)效果。那么,你是不是有思路了呢?沒錯(cuò),可以創(chuàng)建 Buff 類,傳入英雄類后獲得一個(gè)新的增強(qiáng)后的英雄類。

 
 
 
 
  1. class RedBuff extends Buff { 
  2.     constructor(hero) { 
  3.         this.hero = hero; 
  4.     } 
  5.     // 紅buff造成額外傷害 
  6.     extraDamage() { 
  7.     }
  8.      attack() { 
  9.         returnthis.hero.attack() + this.extraDamage(); 
  10.     } 
  11. class BlueBuff extends Buff { 
  12.     constructor(hero) { 
  13.         this.hero = hero; 
  14.     } 
  15.     // 技能CD(減10%) 
  16.     CDR() { 
  17.         returnthis.hero.CDR() * 0.9; 
  18.     } 
  19. class BaronBuff extends Buff { 
  20.     constructor(hero) { 
  21.         this.hero = hero; 
  22.     } 
  23.     // 回城速度縮短一半 
  24.     backSpeed() { 
  25.         returnthis.hero.backSpeed * 0.5; 
  26.     } 
  27. }

定義好所有的 buff 類之后,就可以直接套用到英雄身上,這樣看起來是不是清爽了很多呢?這種寫法看起來很像函數(shù)組合。

 
 
 
 
  1. const yasuo = new Yasuo(); 
  2. const redYasuo = new RedBuff(yasuo); // 紅buff亞索 
  3. const blueYasuo = new BlueBuff(yasuo); // 藍(lán)buff亞索 
  4. const redBlueYasuo = new BlueBuff(redYasuo); // 紅藍(lán)buff亞索

3. ES7 裝飾器

decorator(裝飾器)是 ES7 中的一個(gè)提案,目前處于 stage-2 階段,提案地址:[JavaScript Decorators][https://github.com/tc39/proposal-decorators]。裝飾器與之前講過的函數(shù)組合(compose)以及高階函數(shù)很相似。裝飾器使用 @ 作為標(biāo)識(shí)符,被放置在被裝飾代碼前面。在其他語言中,早就已經(jīng)有了比較成熟的裝飾器方案。

3.1 Python 中的裝飾器

先來看一下 Python 中的一個(gè)裝飾器的例子:

 
 
 
 
  1. def auth(func): 
  2.     def inner(request,*args,**kwargs): 
  3.         v = request.COOKIES.get('user') 
  4.         if not v: 
  5.             return redirect('/login') 
  6.         return func(request, *args,**kwargs) 
  7.     return inner 
  8. @auth 
  9. def index(request): 
  10.     v = request.COOKIES.get("user") 
  11.     return render(request,"index.html",{"current_user":v})

這個(gè) auth 裝飾器是通過檢查 cookie 來判斷用戶是否登錄的。auth 函數(shù)是一個(gè)高階函數(shù),它接收了一個(gè) func 函數(shù)作為參數(shù),返回了一個(gè)新的 inner 函數(shù)。在 inner 函數(shù)中進(jìn)行 cookie 的檢查,由此來判斷是跳回登錄頁面還是繼續(xù)執(zhí)行 func 函數(shù)。在所有需要權(quán)限驗(yàn)證的函數(shù)上,都可以使用這個(gè) auth 裝飾器,很簡潔明了且無侵入。

3.2 JavaScript 裝飾器

JavaScript 中的裝飾器和 Python 的裝飾器類似,依賴于 Object.defineProperty,一般是用來裝飾類、類屬性、類方法。使用裝飾器可以做到不直接修改代碼,就實(shí)現(xiàn)某些功能,做到真正的面向切面編程。這在一定程度上和 Proxy 很相似,但使用起來比 Proxy 會(huì)更加簡潔。

注意:裝飾器目前還處于 stage-2,意味著語法之后也許會(huì)有變動(dòng)。裝飾器用于函數(shù)、對(duì)象等等已經(jīng)有一些規(guī)劃,請(qǐng)看:[Future built-in decorators][https://github.com/tc39/proposal-decorators/blob/master/NEXTBUILTINS.md#applying-built-in-decorators-to-other-syntactic-forms]

3.3 類裝飾器

裝飾類的時(shí)候,裝飾器方法一般會(huì)接收一個(gè)目標(biāo)類作為參數(shù)。下面是一個(gè)給目標(biāo)類增加靜態(tài)屬性 test 的例子:

 
 
 
 
  1. const decoratorClass = (targetClass) => { 
  2.     targetClass.test = '123' 
  3. @decoratorClass 
  4. class Test {} 
  5. Test.test; // '123'

除了可以修改類本身,還可以通過修改原型,給實(shí)例增加新屬性。下面是給目標(biāo)類增加 speak 方法的例子:

 
 
 
 
  1. const withSpeak = (targetClass) => { 
  2.     const prototype = targetClass.prototype; 
  3.     prototype.speak = function() { 
  4.         console.log('I can speak ', this.language); 
  5.     } 
  6. @withSpeak 
  7. class Student { 
  8.     constructor(language) { 
  9.         this.language = language; 
  10.     }
  11.  } 
  12. const student1 = new Student('Chinese'); 
  13. const student2 = new Student('English'); 
  14. student1.speak(); // I can speak  Chinese 
  15. student2.speak(); // I can speak  Chinese

利用高階函數(shù)的屬性,還可以給裝飾器傳參,通過參數(shù)來判斷對(duì)類進(jìn)行什么處理。

 
 
 
 
  1. const withLanguage = (language) =>(targetClass) => { 
  2.     targetClass.prototype.language = language; 
  3. @withLanguage('Chinese') 
  4. class Student { 
  5. const student = new Student(); 
  6. student.language; // 'Chinese'

如果你經(jīng)常編寫 react-redux 的代碼,那么也會(huì)遇到需要將 store 中的數(shù)據(jù)映射到組件中的情況。connect 是一個(gè)高階組件,它接收了兩個(gè)函數(shù) mapStateToProps 和 mapDispatchToProps 以及一個(gè)組件 App,最終返回了一個(gè)增強(qiáng)版的組件。

 
 
 
 
  1. class App extends React.Component { 
  2. connect(mapStateToProps, mapDispatchToProps)(App)

有了裝飾器之后,connect 的寫法可以變得更加優(yōu)雅。

 
 
 
 
  1. @connect(mapStateToProps, mapDispatchToProps) 
  2. class App extends React.Component { 
  3. }

3.4 類屬性裝飾器

類屬性裝飾器可以用在類的屬性、方法、get/set 函數(shù)中,一般會(huì)接收三個(gè)參數(shù):

  1.  target:被修飾的類
  2.  name:類成員的名字
  3.  descriptor:屬性描述符,對(duì)象會(huì)將這個(gè)參數(shù)傳給 Object.defineProperty使用類屬性裝飾器可以做到很多有意思的事情,比如最開始舉的那個(gè) readonly 的例子:
 
 
 
 
  1. function readonly(target, name, descriptor) { 
  2.   descriptor.writable = false; 
  3.   return descriptor; 
  4. class Person { 
  5.     @readonly name = 'person' 
  6. const person = new Person(); 
  7. person.name = 'tom';

還可以用來統(tǒng)計(jì)一個(gè)函數(shù)的執(zhí)行時(shí)間,以便于后期做一些性能優(yōu)化。

 
 
 
 
  1. function time(target, name, descriptor) { 
  2.     const func = descriptor.value; 
  3.     if (typeof func === 'function') { 
  4.         descriptor.value = function(...args) { 
  5.             console.time(); 
  6.             const results = func.apply(this, args); 
  7.             console.timeEnd(); 
  8.             return results; 
  9.         } 
  10.     } 
  11. class Person { 
  12.     @time 
  13.     say() { 
  14.         console.log('hello') 
  15.     } 
  16. const person = new Person(); 
  17. person.say();

在 react 老牌的狀態(tài)管理庫 mobx 中,也通過裝飾器來將類屬性置為可觀察屬性,以此來實(shí)現(xiàn)響應(yīng)式編程。

 
 
 
 
  1. import { 
  2.     observable, 
  3.     action, 
  4.     autorun 
  5. } from 'mobx' 
  6. class Store { 
  7.     @observable count = 1; 
  8.     @action 
  9.     changeCount(count) { 
  10.         this.count = count; 
  11.     } 
  12. const store = new Store(); 
  13. autorun(() => { 
  14.     console.log('count is ', store.count); 
  15. })
  16.  store.changeCount(10); // 修改 count 的值,會(huì)引起 autorun 中的函數(shù)自動(dòng)執(zhí)行。

3.5 裝飾器組合

如果你想要使用多個(gè)裝飾器,那么該怎么辦呢?裝飾器是可以疊加的,根據(jù)離被裝飾類/屬性的距離來依次執(zhí)行。

 
 
 
 
  1. class Person { 
  2.     @time 
  3.     @log 
  4.     say() {} 
  5. }

除此之外,在裝飾器的提案中,還出現(xiàn)了一種組合了多種裝飾器的裝飾器例子。目前還沒見到被使用。通過使用 decorator 來聲明一個(gè)組合裝飾器 xyz,這個(gè)裝飾器組合了多種裝飾器。

 
 
 
 
  1. decorator @xyz(arg, arg2 { 
  2.   @foo @bar(arg) @baz(arg2) 
  3. @xyz(1, 2) class C { }

和下面這種寫法是一樣的。

 
 
 
 
  1. @foo @bar(1) @baz(2)
  2.  class C { }

4. 裝飾器可以做哪些有意思的事情?

4.1 多重繼承

之前我們?cè)谥v解 JavaScript 多重繼承的時(shí)候,使用過 mixin 的方式,這里結(jié)合裝飾器甚至還能更進(jìn)一步簡化 mixin 的使用。mixin 方法將會(huì)接收一個(gè)父類列表,用其裝飾目標(biāo)類。我們理想中的用法應(yīng)該是這樣:

 
 
 
 
  1. @mixin(Parent1, Parent2, Parent3) 
  2. class Child {}

和之前實(shí)現(xiàn)多重繼承的時(shí)候?qū)崿F(xiàn)原理一致,只需要拷貝父類的原型屬性和實(shí)例屬性就可以實(shí)現(xiàn)了。這里創(chuàng)建了一個(gè)新的 Mixin 類,來將 mixins 和 targetClass 上面的所有屬性都拷貝過去。

 
 
 
 
  1. const mixin = (...mixins) => (targetClass) => { 
  2.   mixins = [targetClass, ...mixins]; 
  3.   function copyProperties(target, source) { 
  4.     for (let key of Reflect.ownKeys(source)) { 
  5.       if (key !== 'constructor' 
  6.         && key !== 'prototype' 
  7.         && key !== 'name' 
  8.       ) { 
  9.         let desc = Object.getOwnPropertyDescriptor(source, key); 
  10.         Object.defineProperty(target, key, desc); 
  11.       } 
  12.     } 
  13.   } 
  14.   class Mixin { 
  15.     constructor(...args) { 
  16.       for (let mixin of mixins) { 
  17.         copyProperties(this, new mixin(...args)); // 拷貝實(shí)例屬性 
  18.       } 
  19.     } 
  20.   } 
  21.   for (let mixin of mixins) { 
  22.     copyProperties(Mixin, mixin); // 拷貝靜態(tài)屬性 
  23.     copyProperties(Mixin.prototype, mixin.prototype); // 拷貝原型屬性 
  24.   } 
  25.   return Mixin; 
  26. export default mixin

我們來測(cè)試一下這個(gè) mixin 方法是否能夠正常工作吧。

 
 
 
 
  1. class Parent1 { 
  2.     p1() { 
  3.         console.log('this is parent1') 
  4.     } 
  5. class Parent2 { 
  6.     p2() { 
  7.         console.log('this is parent2') 
  8.     } 
  9. class Parent3 { 
  10.     p3() { 
  11.         console.log('this is parent3') 
  12.     } 
  13. @mixin(Parent1, Parent2, Parent3) 
  14. class Child { 
  15.     c1 = () => { 
  16.         console.log('this is child') 
  17.     } 
  18. const child = new Child(); 
  19. console.log(child);

最終在瀏覽器中打印出來的 child 對(duì)象是這樣的,證明了這個(gè) mixin 是可以正常工作的。

注意:這里的 Child 類就是前面的 Mixin 類。

image.png-69.4kB

也許你會(huì)問,為什么還要多創(chuàng)建一個(gè)多余的 Mixin 類呢?為什么不能直接修改 targetClass 的 constructor 呢?前面不是講過 Proxy 可以攔截 constructor 嗎?恭喜你,你已經(jīng)想到了 Proxy 的一種使用場(chǎng)景。沒錯(cuò),這里用 Proxy 的確會(huì)更加優(yōu)雅。

 
 
 
 
  1. const mixin = (...mixins) =>(targetClass) => { 
  2.     function copyProperties(target, source) { 
  3.         for (let key ofReflect.ownKeys(source)) { 
  4.           if ( key !== 'constructor' 
  5.             && key !== 'prototype' 
  6.             && key !== 'name' 
  7.           ) { 
  8.             let desc = Object.getOwnPropertyDescriptor(source, key); 
  9.             Object.defineProperty(target, key, desc); 
  10.           } 
  11.         } 
  12.       }   
  13.       for (let mixin of mixins) { 
  14.         copyProperties(targetClass, mixin); // 拷貝靜態(tài)屬性 
  15.         copyProperties(targetClass.prototype, mixin.prototype); // 拷貝原型屬性 
  16.       } 
  17.       // 攔截 construct 方法,進(jìn)行實(shí)例屬性的拷貝 
  18.       returnnewProxy(targetClass, { 
  19.         construct(target, args) { 
  20.           const obj = new target(...args); 
  21.           for (let mixin of mixins) { 
  22.               copyProperties(obj, new mixin()); // 拷貝實(shí)例屬性 
  23.           } 
  24.           return obj; 
  25.         } 
  26.       }); 
  27. }

4.2 防抖和節(jié)流

以往我們?cè)陬l繁觸發(fā)的場(chǎng)景下,為了優(yōu)化性能,經(jīng)常會(huì)使用到節(jié)流函數(shù)。下面以 React 組件綁定滾動(dòng)事件為例子:

 
 
 
 
  1. class App extends React.Component { 
  2.     componentDidMount() { 
  3.         this.handleScroll = _.throttle(this.scroll, 500); 
  4.         window.addEveneListener('scroll', this.handleScroll); 
  5.     } 
  6.     componentWillUnmount() { 
  7.         window.removeEveneListener('scroll', this.handleScroll); 
  8.     } 
  9.     scroll() {} 
  10. }

在組件中綁定事件需要注意應(yīng)當(dāng)在組件銷毀的時(shí)候進(jìn)行解綁。而由于節(jié)流函數(shù)返回了一個(gè)新的匿名函數(shù),所以為了之后能夠有效解綁,不得不將這個(gè)匿名函數(shù)存起來,以便于之后使用。但是在有了裝飾器之后,我們就不必在每個(gè)綁定事件的地方都手動(dòng)設(shè)置 throttle 方法,只需要在 scroll 函數(shù)添加一個(gè) throttle 的裝飾器就行了。

 
 
 
 
  1. const throttle = (time) => { 
  2.     let prev = newDate(); 
  3.     return(target, name, descriptor) => { 
  4.         const func = descriptor.value; 
  5.         if (typeof func === 'function') { 
  6.             descriptor.value = function(...args) { 
  7.                 const now = newDate(); 
  8.                 if (now - prev > wait) { 
  9.                     fn.apply(this, args); 
  10.                     prev = newDate(); 
  11.                 } 
  12.             } 
  13.         } 
  14.     } 
  15. }

使用起來比原來要簡潔很多。

 
 
 
 
  1. class App extends React.Component { 
  2.     componentDidMount() { 
  3.         window.addEveneListener('scroll', this.scroll); 
  4.     } 
  5.     componentWillUnmount() { 
  6.         window.removeEveneListener('scroll', this.scroll); 
  7.     } 
  8.     @throttle(50) 
  9.     scroll() {} 
  10. }

而實(shí)現(xiàn)防抖(debounce)函數(shù)裝飾器和節(jié)流函數(shù)類似,這里也不再多說。

 
 
 
 
  1. const debounce = (time) => { 
  2.     let timer; 
  3.     return(target, name, descriptor) => { 
  4.         const func = descriptor.value; 
  5.         if (typeof func === 'function') { 
  6.             descriptor.value = function(...args) { 
  7.                 if(timer) clearTimeout(timer) 
  8.                 timer = setTimeout(()=> { 
  9.                     fn.apply(this, args) 
  10.                 }, wait) 
  11.             } 
  12.         } 
  13.     } 
  14. }

如果對(duì)節(jié)流和防抖函數(shù)比較感興趣,那么可以去閱讀一下這篇文章:[函數(shù)節(jié)流與函數(shù)防抖][https://juejin.im/entry/58c0379e44d9040068dc952f]

4.3 數(shù)據(jù)格式驗(yàn)證

通過類屬性裝飾器來對(duì)類的屬性進(jìn)行類型的校驗(yàn)。

 
 
 
 
  1. const validate = (type) =>(target, name) => { 
  2.     if (typeof target[name] !== type) { 
  3.         thrownewError(`attribute ${name} must be ${type} type`) 
  4.     } 
  5. class Form { 
  6.     @validate('string') 
  7.     name = 111// Error: attribute name must be ${type} type 
  8. }

如果你覺得對(duì)屬性一個(gè)個(gè)手動(dòng)去校驗(yàn)太過麻煩,也可以通過編寫校驗(yàn)規(guī)則,來對(duì)整個(gè)類進(jìn)行校驗(yàn)。

 
 
 
 
  1. const rules = { 
  2.     name: 'string', 
  3.     password: 'string', 
  4.     age: 'number' 
  5. const validator = rules =>targetClass => { 
  6.     returnnewProxy(targetClass, { 
  7.         construct(target, args) { 
  8.             const obj = new target(...args); 
  9.             for (let [name, type] ofObject.entries(rules)) { 
  10.                 if (typeof obj[name] !== type) { 
  11.                     thrownewError(`${name} must be ${type}`) 
  12.                 } 
  13.             } 
  14.             return obj; 
  15.         } 
  16.     }) 
  17. }
  18. @validator(rules) 
  19. class Person { 
  20.     name = 'tom' 
  21.     password = '123' 
  22.     age = '21' 
  23. const person = new Person();

4.4 core-decorators.js

core-decorators 是一個(gè)封裝了常用裝飾器的 JS 庫,它歸納了下面這些裝飾器(只列舉了部分)。

  1.  autobind:自動(dòng)綁定 this,告別箭頭函數(shù)和 bind
  2.  readonly:將類屬性設(shè)置為只讀
  3.  override:檢查子類的方法是否正確覆蓋了父類的同名方法
  4.  debounce:防抖函數(shù)
  5.  throttle:節(jié)流函數(shù)
  6.  enumerable:讓一個(gè)類方法變得可枚舉
  7.  nonenumerable:讓一個(gè)類屬性變得不可枚舉
  8.  time:打印函數(shù)執(zhí)行耗時(shí)
  9.  mixin:將多個(gè)對(duì)象混入類(和我們上面的 mixin 不太一樣)

分享題目:一文讀懂JS裝飾器,這是一個(gè)會(huì)打扮的裝飾器
本文網(wǎng)址:http://m.5511xx.com/article/cojgeoh.html