中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么理解并掌握JS裝飾器

發布時間:2021-11-04 16:22:31 來源:億速云 閱讀:174 作者:iii 欄目:web開發

本篇內容介紹了“怎么理解并掌握JS裝飾器”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

1. 前言

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

怎么理解并掌握JS裝飾器

下面是一個使用裝飾器的簡單例子,這個 @readonly 可以將 count 屬性設置為只讀。可以看出來,裝飾器大大提高了代碼的簡潔性和可讀性。

class Person {      @readonly count = 0;  }

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

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

2. 裝飾器模式

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

2.1 一個英雄聯盟的例子

下班回去和朋友愉快地開黑,當我正在用亞索「面對疾風吧」的時候,突然想到,如果讓我設計亞索英雄,我該怎么實現呢?

我想了想,肯定會先設計一個英雄的類。

class Hero {      attack() {}  }

然后,再實現一個 Yasuo 的類來繼承這個 Hero 類。

class Yasuo extends Hero {      attack() {          console.log("斬鋼閃");      }  }

我還在想這個問題的時候,隊友已經打了大龍,我的亞索身上就出現了大龍 buff 的印記。我突然想到,那該怎么給英雄增加大龍 buff 呢?那增加個大龍 buff 的屬性不行嗎?當然不太行,要知道,英雄聯盟里面的大龍 buff 是會增加收益的。嗯,聰明的我已經想到辦法了,再繼承一次不就好了嗎?

class BaronYasuo extends Yasuo {  }

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

怎么理解并掌握JS裝飾器

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

怎么理解并掌握JS裝飾器

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

class RedBuff extends Buff {      constructor(hero) {          this.hero = hero;      }      // 紅buff造成額外傷害      extraDamage() {      }      attack() {          returnthis.hero.attack() + this.extraDamage();      }  }  class BlueBuff extends Buff {      constructor(hero) {          this.hero = hero;      }      // 技能CD(減10%)      CDR() {          returnthis.hero.CDR() * 0.9;      }  }  class BaronBuff extends Buff {      constructor(hero) {          this.hero = hero;      }      // 回城速度縮短一半      backSpeed() {          returnthis.hero.backSpeed * 0.5;      }  }

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

const yasuo = new Yasuo();  const redYasuo = new RedBuff(yasuo); // 紅buff亞索  const blueYasuo = new BlueBuff(yasuo); // 藍buff亞索  const redBlueYasuo = new BlueBuff(redYasuo); // 紅藍buff亞索

3. ES7 裝飾器

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

3.1 Python 中的裝飾器

先來看一下 Python 中的一個裝飾器的例子:

def auth(func):      def inner(request,*args,**kwargs):          v = request.COOKIES.get('user')          if not v:              return redirect('/login')          return func(request, *args,**kwargs)      return inner  @auth  def index(request):      v = request.COOKIES.get("user")      return render(request,"index.html",{"current_user":v})

怎么理解并掌握JS裝飾器

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

3.2 JavaScript 裝飾器

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

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

3.3 類裝飾器

裝飾類的時候,裝飾器方法一般會接收一個目標類作為參數。下面是一個給目標類增加靜態屬性 test 的例子:

const decoratorClass = (targetClass) => {      targetClass.test = '123'  }  @decoratorClass  class Test {}  Test.test; // '123'

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

const withSpeak = (targetClass) => {      const prototype = targetClass.prototype;      prototype.speak = function() {          console.log('I can speak ', this.language);      }  }  @withSpeak  class Student {      constructor(language) {          this.language = language;      }  }  const student1 = new Student('Chinese');  const student2 = new Student('English');  student1.speak(); // I can speak  Chinese  student2.speak(); // I can speak  Chinese

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

const withLanguage = (language) =>(targetClass) => {      targetClass.prototype.language = language;  }  @withLanguage('Chinese')  class Student {  }  const student = new Student();  student.language; // 'Chinese'

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

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

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

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

3.4 類屬性裝飾器

類屬性裝飾器可以用在類的屬性、方法、get/set 函數中,一般會接收三個參數:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2.  target:被修飾的類

  3.  name:類成員的名字

  4.  descriptor:屬性描述符,對象會將這個參數傳給 Object.defineProperty使用類屬性裝飾器可以做到很多有意思的事情,比如最開始舉的那個 readonly 的例子: 

function readonly(target, name, descriptor) {    descriptor.writable = false;    return descriptor;  }  class Person {      @readonly name = 'person'  }  const person = new Person();  person.name = 'tom';

還可以用來統計一個函數的執行時間,以便于后期做一些性能優化。

function time(target, name, descriptor) {      const func = descriptor.value;      if (typeof func === 'function') {          descriptor.value = function(...args) {              console.time();              const results = func.apply(this, args);              console.timeEnd();              return results;          }      }  }  class Person {      @time      say() {          console.log('hello')      }  }  const person = new Person();  person.say();

在 react 知名的狀態管理庫 mobx 中,也通過裝飾器來將類屬性置為可觀察屬性,以此來實現響應式編程。

import {      observable,      action,      autorun  } from 'mobx'  class Store {      @observable count = 1;      @action      changeCount(count) {          this.count = count;      }  }  const store = new Store();  autorun(() => {      console.log('count is ', store.count);  })  store.changeCount(10); // 修改 count 的值,會引起 autorun 中的函數自動執行。

3.5 裝飾器組合

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

class Person {      @time      @log      say() {}  }

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

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

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

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

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

4.1 多重繼承

之前我們在講解 JavaScript 多重繼承的時候,使用過 mixin 的方式,這里結合裝飾器甚至還能更進一步簡化 mixin 的使用。mixin 方法將會接收一個父類列表,用其裝飾目標類。我們理想中的用法應該是這樣:

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

和之前實現多重繼承的時候實現原理一致,只需要拷貝父類的原型屬性和實例屬性就可以實現了。這里創建了一個新的 Mixin 類,來將 mixins 和 targetClass 上面的所有屬性都拷貝過去。

const mixin = (...mixins) => (targetClass) => {    mixins = [targetClass, ...mixins];    function copyProperties(target, source) {      for (let key of Reflect.ownKeys(source)) {        if (key !== 'constructor'          && key !== 'prototype'          && key !== 'name'        ) {          let desc = Object.getOwnPropertyDescriptor(source, key);          Object.defineProperty(target, key, desc);        }      }    }    class Mixin {      constructor(...args) {        for (let mixin of mixins) {          copyProperties(this, new mixin(...args)); // 拷貝實例屬性        }      }    }    for (let mixin of mixins) {      copyProperties(Mixin, mixin); // 拷貝靜態屬性      copyProperties(Mixin.prototype, mixin.prototype); // 拷貝原型屬性    }    return Mixin;  }  export default mixin

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

class Parent1 {      p1() {          console.log('this is parent1')      }  }  class Parent2 {      p2() {          console.log('this is parent2')      }  }  class Parent3 {      p3() {          console.log('this is parent3')      }  }  @mixin(Parent1, Parent2, Parent3)  class Child {      c1 = () => {          console.log('this is child')      }  }  const child = new Child();  console.log(child);

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

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

怎么理解并掌握JS裝飾器

image.png-69.4kB

也許你會問,為什么還要多創建一個多余的 Mixin 類呢?為什么不能直接修改 targetClass 的 constructor 呢?前面不是講過 Proxy 可以攔截 constructor 嗎?恭喜你,你已經想到了 Proxy 的一種使用場景。沒錯,這里用 Proxy 的確會更加優雅。

const mixin = (...mixins) =>(targetClass) => {      function copyProperties(target, source) {          for (let key ofReflect.ownKeys(source)) {            if ( key !== 'constructor'              && key !== 'prototype'              && key !== 'name'            ) {              let desc = Object.getOwnPropertyDescriptor(source, key);              Object.defineProperty(target, key, desc);            }          }        }          for (let mixin of mixins) {          copyProperties(targetClass, mixin); // 拷貝靜態屬性          copyProperties(targetClass.prototype, mixin.prototype); // 拷貝原型屬性        }        // 攔截 construct 方法,進行實例屬性的拷貝        returnnewProxy(targetClass, {          construct(target, args) {            const obj = new target(...args);            for (let mixin of mixins) {                copyProperties(obj, new mixin()); // 拷貝實例屬性            }            return obj;          }        });  }

4.2 防抖和節流

以往我們在頻繁觸發的場景下,為了優化性能,經常會使用到節流函數。下面以 React 組件綁定滾動事件為例子:

class App extends React.Component {      componentDidMount() {          this.handleScroll = _.throttle(this.scroll, 500);          window.addEveneListener('scroll', this.handleScroll);      }      componentWillUnmount() {          window.removeEveneListener('scroll', this.handleScroll);      }      scroll() {}  }

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

const throttle = (time) => {      let prev = newDate();      return(target, name, descriptor) => {          const func = descriptor.value;          if (typeof func === 'function') {              descriptor.value = function(...args) {                  const now = newDate();                  if (now - prev > wait) {                      fn.apply(this, args);                      prev = newDate();                  }              }          }      }  }

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

class App extends React.Component {      componentDidMount() {          window.addEveneListener('scroll', this.scroll);      }      componentWillUnmount() {          window.removeEveneListener('scroll', this.scroll);      }      @throttle(50)      scroll() {}  }

而實現防抖(debounce)函數裝飾器和節流函數類似,這里也不再多說。

const debounce = (time) => {      let timer;      return(target, name, descriptor) => {          const func = descriptor.value;          if (typeof func === 'function') {              descriptor.value = function(...args) {                  if(timer) clearTimeout(timer)                  timer = setTimeout(()=> {                      fn.apply(this, args)                  }, wait)              }          }      }  }

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

4.3 數據格式驗證

通過類屬性裝飾器來對類的屬性進行類型的校驗。

const validate = (type) =>(target, name) => {      if (typeof target[name] !== type) {          thrownewError(`attribute ${name} must be ${type} type`)      }  }  class Form {      @validate('string')      name = 111// Error: attribute name must be ${type} type  }

如果你覺得對屬性一個個手動去校驗太過麻煩,也可以通過編寫校驗規則,來對整個類進行校驗。

怎么理解并掌握JS裝飾器

const rules = {      name: 'string',      password: 'string',      age: 'number'  }  const validator = rules =>targetClass => {      returnnewProxy(targetClass, {          construct(target, args) {              const obj = new target(...args);              for (let [name, type] ofObject.entries(rules)) {                  if (typeof obj[name] !== type) {                      thrownewError(`${name} must be ${type}`)                  }              }              return obj;          }      })  } @validator(rules)  class Person {      name = 'tom'      password = '123'      age = '21'  }  const person = new Person();

4.4 core-decorators.js

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

  1.  autobind:自動綁定 this,告別箭頭函數和 bind

  2.  readonly:將類屬性設置為只讀

  3.  override:檢查子類的方法是否正確覆蓋了父類的同名方法

  4.  debounce:防抖函數

  5.  throttle:節流函數

  6.  enumerable:讓一個類方法變得可枚舉

  7.  nonenumerable:讓一個類屬性變得不可枚舉

  8.  time:打印函數執行耗時

  9.  mixin:將多個對象混入類(和我們上面的 mixin 不太一樣) 

“怎么理解并掌握JS裝飾器”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

js
AI

千阳县| 苏尼特右旗| 交口县| 滨州市| 贡嘎县| 长寿区| 庆云县| 伊金霍洛旗| 台州市| 兰坪| 凭祥市| 安庆市| 油尖旺区| 拉萨市| 航空| 上林县| 科尔| 兰西县| 澄迈县| 凤阳县| 衡阳县| 江孜县| 台南县| 宣城市| 玛曲县| 溧水县| 庆云县| 安丘市| 青河县| 桦川县| 崇左市| 平湖市| 五大连池市| 鄂托克前旗| 泰宁县| 霞浦县| 长武县| 丹阳市| 新巴尔虎左旗| 武清区| 太仓市|