日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

JS 装饰器(Decorator)场景实战

發(fā)布時(shí)間:2023/12/20 javascript 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JS 装饰器(Decorator)场景实战 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文不會(huì)大篇幅介紹裝飾器(Decorator)的概念和基礎(chǔ)用法,核心介紹我們團(tuán)隊(duì)如何將裝飾器應(yīng)用于實(shí)際開(kāi)發(fā),和一些高級(jí)用法的實(shí)現(xiàn)。


裝飾器簡(jiǎn)介

Decorator 是 ES7 的一個(gè)新語(yǔ)法,正如其“裝飾器”的叫法所表達(dá)的,他可以對(duì)一些對(duì)象進(jìn)行裝飾包裝然后返回一個(gè)被包裝過(guò)的對(duì)象,可以裝飾的對(duì)象包括:類(lèi),屬性,方法等。Decorator 的寫(xiě)法與 Java 里的注解(Annotation)非常類(lèi)似,但是一定不要把 JS 中的裝飾器叫做是“注解”,因?yàn)檫@兩者的原理和實(shí)現(xiàn)的功能還是有所區(qū)別的,在 Java 中,注解主要是對(duì)某個(gè)對(duì)象進(jìn)行標(biāo)注,然后在運(yùn)行時(shí)或者編譯時(shí),可以通過(guò)例如反射這樣的機(jī)制拿到被標(biāo)注的對(duì)象,對(duì)其進(jìn)行一些邏輯包裝。而 Decorator 的原理和作用則更為簡(jiǎn)單,就是包裝對(duì)象,然后返回一個(gè)新的對(duì)象描述(descriptor),其作用也非常單一簡(jiǎn)單,基本上就是獲取包裝對(duì)象的宿主、鍵值幾個(gè)有限的信息。

關(guān)于 Decorator 的詳細(xì)介紹參見(jiàn)文章:zhuanlan.zhihu.com/FrontendMag…

簡(jiǎn)單來(lái)說(shuō),JS 的裝飾器可以用來(lái)“裝飾”三種類(lèi)型的對(duì)象:類(lèi)的屬性/方法、訪(fǎng)問(wèn)器、類(lèi)本身,簡(jiǎn)單看幾個(gè)例子吧。

針對(duì)屬性/方法的裝飾器

// decorator 外部可以包裝一個(gè)函數(shù),函數(shù)可以帶參數(shù) function Decorator(type){/*** 這里是真正的 decorator* @target 裝飾的屬性所述的類(lèi)的原型,注意,不是實(shí)例后的類(lèi)。如果裝飾的是 Car 的某個(gè)屬性,這個(gè) target 的值就是 Car.prototype* @name 裝飾的屬性的 key* @descriptor 裝飾的對(duì)象的描述對(duì)象*/return function (target, name, descriptor){// 以此可以獲取實(shí)例化的時(shí)候此屬性的默認(rèn)值let v = descriptor.initializer && descriptor.initializer.call(this);// 返回一個(gè)新的描述對(duì)象,或者直接修改 descriptor 也可以return {enumerable: true,configurable: true,get: function() {return v;},set: function(c) {v = c;}}} }復(fù)制代碼

注意這里的 target 對(duì)應(yīng)的是被裝飾的屬性所屬類(lèi)的原型,如果是裝飾一個(gè) A 類(lèi)的屬性,并且 A 類(lèi)是繼承自 B 類(lèi)的,這時(shí)候你打印 target,獲取到的是 A.prototype,它的結(jié)構(gòu)是這樣的,這里一定要注意:

[image:A944761A-E0FA-4C04-BD90-BE179C46B641-35651-00001223828250C5/187FCC2A-8CC4-46C4-B8A3-A7FD5E0376F6.png]
如果需要操作 target,可能需要搞清楚這個(gè)問(wèn)題。

針對(duì) 訪(fǎng)問(wèn)操作符的裝飾

與屬性方法類(lèi)似,就不詳述了。

class Person {@nonenumerableget kidCount() { return this.children.length; } }function nonenumerable(target, name, descriptor) {descriptor.enumerable = false;return descriptor; }復(fù)制代碼

針對(duì)類(lèi)的裝飾

// 例如 mobx 中 @observer 的用法 /*** 包裝 react 組件* @param target*/ function observer(target) {target.prototype.componentWillMount = function() {targetCWM && targetCWM.call(this);ReactMixin.componentWillMount.call(this);}; }復(fù)制代碼

其中的 target 就是類(lèi)本身(而不是其 prototype)


真實(shí)場(chǎng)景應(yīng)用

今天,我們要介紹的主要是,如何將 Decorator 這個(gè)特性應(yīng)用于數(shù)據(jù)定義層,實(shí)現(xiàn)一些類(lèi)似于類(lèi)型檢查、字段映射等功能。

關(guān)于數(shù)據(jù)定義層(Model),其實(shí)就是應(yīng)用內(nèi)出現(xiàn)的各種實(shí)體數(shù)據(jù)的定義,也就是 MVVM 中的 M 層,注意,和 VM 層做好區(qū)分,Model 本身不提供數(shù)據(jù)的管理和流通,只負(fù)責(zé)定義某個(gè)實(shí)體本身的屬性和方法,例如頁(yè)面里有一輛車(chē)的模塊,我們就定義一個(gè) CarModel,它用來(lái)描述車(chē)輛的顏色、價(jià)格、品牌等信息。

關(guān)于為什么要在前端應(yīng)用內(nèi)定義明確的 Model,這個(gè)我之前在知乎上也早有論述,核心幾點(diǎn):

  • 提高可維護(hù)性。將數(shù)據(jù)源頭的實(shí)體做一個(gè)固定而準(zhǔn)確的描述,這個(gè)對(duì)于串聯(lián)理解整個(gè)應(yīng)用非常重要,特別是在重構(gòu)或者接手別人的代碼的時(shí)候,你需要準(zhǔn)確的知道一個(gè)頁(yè)面(或者是一個(gè)模塊)它會(huì)包含哪些數(shù)據(jù),這些數(shù)據(jù)分別有哪些字段,這樣更便于理解整個(gè)應(yīng)用的數(shù)據(jù)邏輯。
  • 提高確定性。當(dāng)你要給你的界面增加幾個(gè)車(chē)輛字段的時(shí)候,你不清楚之前是否已經(jīng)定義過(guò)這些字段,服務(wù)端是否會(huì)返回這些字段,可能要請(qǐng)求一下(并且要有權(quán)限取到所有字段)才能知道,但是如果有 model 的明確定義,有什么字段就一目了然了。
  • 提高開(kāi)發(fā)效率。在這一層統(tǒng)一做一些數(shù)據(jù)映射和類(lèi)型檢查等工作,這也是今天要講的重點(diǎn)。

以我們團(tuán)隊(duì) RN 開(kāi)發(fā)框架中 Model 部分的實(shí)現(xiàn)為例,我們至少提供了三個(gè)基礎(chǔ)的基于 Decorator 的功能:類(lèi)型檢查,單位轉(zhuǎn)換,字段映射。接下來(lái)我會(huì)先簡(jiǎn)單介紹下這幾個(gè)功能是做什么的,隨后介紹如何實(shí)現(xiàn)這些 Decorator。

先來(lái)看看最終調(diào)用時(shí)候的代碼

class CarModel extends BaseModel {/*** 價(jià)格* @type {number}*/@observable@Check(CheckType.Number)@Unit(UnitType.PRICE_UNIT_WY)price = 0;/*** 賣(mài)家名* @type {string}*/@observable@Check(CheckType.String)@ServerName('seller_name')sellerName = ''; }復(fù)制代碼

可以看到我們有三個(gè)自定義的 decorator :

@Unit, // 單位轉(zhuǎn)換裝飾器 @Check, // 類(lèi)型檢查裝飾器, @ServerName // 數(shù)據(jù)字段映射裝飾器,當(dāng)前后端定義的字段名不一致的時(shí)候用復(fù)制代碼

@Unit 是一個(gè)比較特殊的裝飾器,它的作用是在前后端之間自動(dòng)轉(zhuǎn)換單位,也就是前端和后端交換某些帶單位的數(shù)據(jù)的時(shí)候,會(huì)把根據(jù)各端的注解和裝飾器,把真實(shí)值轉(zhuǎn)換成帶單位的值傳給另一端,然后另一端會(huì)在框架層自動(dòng)轉(zhuǎn)成它定義的單位,以此解決前后端單位不一致,交換數(shù)據(jù)時(shí)混亂導(dǎo)致的問(wèn)題。

被 @Unit 裝飾過(guò)的屬性,讀寫(xiě)的時(shí)候都是按照前端的單位讀寫(xiě),然后再轉(zhuǎn)換成 JSON 的時(shí)候就會(huì)特殊處理成類(lèi)似 12.3_$wy 這樣的格式,表示這個(gè)數(shù)的單位是萬(wàn)元。
@Check 更為容易理解,就是用來(lái)檢查字段類(lèi)型,或者檢查字段格式,或者一些自定義檢查,例如正則表達(dá)式等。
@ServerName 則用來(lái)做映射,例如前后端對(duì)同一個(gè)界面元素的命名不同,這時(shí)候不需要完全按照服務(wù)端的命名來(lái)決定,可以在前端用另外一個(gè)屬性名,然后將其裝飾成服務(wù)端的字段名。

基礎(chǔ)實(shí)現(xiàn)

我們的目標(biāo)就是實(shí)現(xiàn)這幾個(gè) Decorator,按照之前對(duì) Decorator 的科普,其實(shí)要獨(dú)立實(shí)現(xiàn)這幾個(gè)功能其實(shí)非常簡(jiǎn)單。
以 @Check 為例,我們改寫(xiě)被包裝屬性的 descriptor,返回一個(gè)新的 descriptor,將被包裝屬性的 getter 和 setter 重新定義,然后在其調(diào)用 setter 的時(shí)候先檢查傳入?yún)?shù)的類(lèi)型和格式,做一些對(duì)應(yīng)的處理。

/*** 此注解如果賦值的時(shí)候匹配到的類(lèi)型有問(wèn)題,會(huì)在控制臺(tái)顯示警告* @param type CheckType 中定義的類(lèi)型* @returns {Function}* @constructor*/ function CheckerDecorator(type){return function (target, name, descriptor){let v = descriptor.initializer && descriptor.initializer.call(this);return {enumerable: true,configurable: true,get: function() {return v;},set: function(c) {// 在此對(duì)傳入的 c 的值做各種檢查var cType = typeof(c);// ...v = c;}}} }復(fù)制代碼

非常簡(jiǎn)單,其他幾個(gè) Decorator 的實(shí)現(xiàn)也類(lèi)似,可能像@Unit 這種實(shí)現(xiàn)起來(lái)會(huì)稍顯復(fù)雜,不過(guò)只要在 Decorator 中記住每個(gè)屬性標(biāo)注的單位,在序列化的時(shí)候獲取對(duì)應(yīng)的屬性對(duì)應(yīng)的單位然后做轉(zhuǎn)換就可以了。

基礎(chǔ)實(shí)現(xiàn)的問(wèn)題

但是,到這里,問(wèn)題其實(shí)還沒(méi)有完!
我們的確實(shí)現(xiàn)了一個(gè)可用的 Decorator,但是這些 Decorator 可以疊加使用嗎?另外可以和業(yè)界常用的一些 Decorator 混用嗎?例如 mobx 中的 @ observable。也就是我上面最開(kāi)始的實(shí)例的用法:

@observable @Check(CheckType.String) @ServerName('seller_name') sellerName = '';復(fù)制代碼

如果你按照我剛才的方式實(shí)現(xiàn) @Check 和 @ServerName 的話(huà),你會(huì)發(fā)現(xiàn)兩個(gè)致命的問(wèn)題:

  • 這兩個(gè)自己實(shí)現(xiàn)的 Decorator 首先就沒(méi)法疊加使用。
  • 這兩個(gè) Decorator 都無(wú)法和 @observable 這個(gè)同時(shí)使用。
    為什么呢?問(wèn)題就出在我們改寫(xiě)屬性的 getter 和 setter 的實(shí)現(xiàn)原理上。首先,每次給一個(gè)屬性定義 getter 和 setter 都會(huì)覆蓋前一次的定義,也就是這個(gè)動(dòng)作只能有一次。然后,mobx 的實(shí)現(xiàn)非常依賴(lài)對(duì) getter 和 setter 的定義(可以參考我之前的文章:如何自己實(shí)現(xiàn)一個(gè) mobx - 原理解析)

事實(shí)上,Decorator 本身疊加使用時(shí)沒(méi)問(wèn)題的,因?yàn)槟愕拿看伟b,都會(huì)將屬性的 descriptor 返回給上一層的包裝,最后就是一個(gè)函數(shù)包函數(shù)包函數(shù)的效果,最終返回的還是這個(gè)屬性的 descriptor 。

進(jìn)階實(shí)現(xiàn)

那我們就需要摒棄掉定義 getter 和 setter 的實(shí)現(xiàn)方式。其實(shí)除了這種方式,還有很多方式可以實(shí)現(xiàn)上述的功能,核心就是一點(diǎn),在裝飾器函數(shù)里,將你需要處理的屬性和對(duì)這個(gè)屬性需要做的處理的對(duì)應(yīng)關(guān)系都記錄下來(lái),然后在處理實(shí)例化數(shù)據(jù)和序列化數(shù)據(jù)的時(shí)候,把對(duì)應(yīng)關(guān)系取出來(lái),執(zhí)行相關(guān)邏輯即可。

廢話(huà)不說(shuō),我們直接上一種將這個(gè)對(duì)應(yīng)關(guān)系掛載到類(lèi)的原型上的一個(gè)實(shí)現(xiàn)方式。

function Check (type) {return function (target, name, descriptor) {let v = descriptor.initializer && descriptor.initializer.call(this);/*** 將屬性名字以及需要的類(lèi)型的對(duì)應(yīng)關(guān)系記錄到類(lèi)的原型上*/if (!target.constructor.__checkers__) {// 將這個(gè)隱藏屬性定義成 not enumerable,遍歷的時(shí)候是取不到的。Object.defineProperty(target.constructor, "__checkers__", {value: {},enumerable: false,writeable: true,configurable: true});}target.constructor.__checkers__[name] = {type: type};return descriptor} }復(fù)制代碼

注意,我前面提到的一個(gè)信息,裝飾函數(shù)的第一個(gè)參數(shù) target 是包裝屬性所屬的類(lèi)的原型(prototype),這個(gè)通過(guò)看 babel 編譯后的結(jié)果可以看到。然后我這里為什么將對(duì)應(yīng)關(guān)系掛載到 target.constructor 上,是因?yàn)槲宜械?Model 類(lèi),都是繼承自我提供的一個(gè) Model 基類(lèi)的(BaseModel),target 拿到的不是子類(lèi)的原型,而是基類(lèi)的原型,target.constructor 拿到的才是最終的子類(lèi)。也就是我把對(duì)應(yīng)關(guān)系掛載到了開(kāi)發(fā)定義的子類(lèi)上。

接下來(lái)看看基類(lèi)的代碼,核心提供兩個(gè)方法,分別是映射數(shù)據(jù)和序列化的方法。

class BaseModel {/*** 將后端數(shù)據(jù)直接映射到當(dāng)前的示例上*/__map (json) {let alias = this.constructor.__aliasNames__;let units = this.constructor.__unitOriginals__;let checkers = this.constructor.__checkers__;for (let i in this) {if (!this.hasOwnProperty(i)) return;// 如果有多層裝飾器,需要經(jīng)過(guò)多個(gè)邏輯處理最終產(chǎn)生一個(gè)最終值 realValuelet realValue = json[i];// 接下來(lái)一步一步處理數(shù)據(jù)// 首先檢查別名數(shù)據(jù),并做映射if (alias && typeof(alias[i]) !== 'undefined') {// ......}// 然后針對(duì)數(shù)據(jù)檢查類(lèi)型if (checkers && checkers[i]) {// ......}// 最終,對(duì)數(shù)據(jù)做單位轉(zhuǎn)換if (units && units[i]) {// ......}// 賦值this[i] = realValue;}}/*** 復(fù)寫(xiě) JSON.stringify 時(shí)自動(dòng)調(diào)用的函數(shù)*/toJSON () {let result = {};let units = this.constructor.__unitOriginals__;for (let i in this) {if (!this.hasOwnProperty(i)) return;if (units && units[i]) {// 序列化時(shí),有需要加單位的加上單位result[i] = this[i] + '_$' + units[i];} else {result[i] = this[i];}}return result;} }復(fù)制代碼

在 __map 函數(shù)中,我們將當(dāng)前類(lèi)(this.constructor)上的對(duì)應(yīng)關(guān)系都取出來(lái),然后做數(shù)據(jù)校驗(yàn)和映射,這里應(yīng)該不難理解了。

最終應(yīng)用的代碼就是我們開(kāi)篇貼出來(lái)最終使用的代碼,只要相應(yīng)的 Model 類(lèi)繼承自 BaseModel 即可。

通過(guò)這樣的方式實(shí)現(xiàn)的 Decorator ,因?yàn)闆](méi)有用到任何 getter setter 相關(guān)的功能,所以可以和 mobx 這樣的庫(kù)完美融合,并且可以無(wú)限疊加使用,不過(guò)如果你用到了多個(gè)三方庫(kù),他們都提供了對(duì)應(yīng)的 Decorator,然后又都修改了 getter 和 setter,那就沒(méi)有辦法了!


總結(jié)

Decorator 雖然原理非常簡(jiǎn)單,但是的確可以實(shí)現(xiàn)很多實(shí)用又方便的功能,目測(cè)前端領(lǐng)域很多框架和庫(kù)都會(huì)大規(guī)模使用這個(gè)特性,但是也希望這些庫(kù)在實(shí)現(xiàn) Decorator 的時(shí)候考慮下通用性,考慮下疊加和共存的問(wèn)題。像上面 mobx 的 @observable,不關(guān)無(wú)法疊加,而且和我自己實(shí)現(xiàn)的 Decorator 的順序都不能亂,必須在最外層,因?yàn)樗淖兞苏麄€(gè)屬性的性質(zhì),不寫(xiě)在最外層的時(shí)候,會(huì)發(fā)現(xiàn)一些莫名其妙的問(wèn)題。

總結(jié)

以上是生活随笔為你收集整理的JS 装饰器(Decorator)场景实战的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。