<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當(dāng)前位置: 首頁 - 科技 - 知識(shí)百科 - 正文

        Javascript裝飾器的用法

        來源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-27 19:34:08
        文檔

        Javascript裝飾器的用法

        Javascript裝飾器的用法:這篇文章主要介紹了關(guān)于Javascript裝飾器的用法,有著一定的參考價(jià)值,現(xiàn)在分享給大家,有需要的朋友可以參考一下最近新開了一個(gè)Node項(xiàng)目,采用TypeScript來開發(fā),在數(shù)據(jù)庫(kù)及路由管理方面用了不少的裝飾器,發(fā)覺這的確是一個(gè)好東西。 裝飾器是一個(gè)還處于草案
        推薦度:
        導(dǎo)讀Javascript裝飾器的用法:這篇文章主要介紹了關(guān)于Javascript裝飾器的用法,有著一定的參考價(jià)值,現(xiàn)在分享給大家,有需要的朋友可以參考一下最近新開了一個(gè)Node項(xiàng)目,采用TypeScript來開發(fā),在數(shù)據(jù)庫(kù)及路由管理方面用了不少的裝飾器,發(fā)覺這的確是一個(gè)好東西。 裝飾器是一個(gè)還處于草案

        因?yàn)?code>@符號(hào)后邊跟的是一個(gè)函數(shù)的引用,所以對(duì)于mixin的實(shí)現(xiàn),我們可以很輕易的使用閉包來實(shí)現(xiàn):

        class A { say() { return 1 } }
        class B { hi() { return 2 } }
        
        @mixin(A, B)
        class C { }
        
        function mixin(...args) {
         // 調(diào)用函數(shù)返回裝飾器實(shí)際應(yīng)用的函數(shù)
         return function(constructor) {
         for (let arg of args) {
         for (let key of Object.getOwnPropertyNames(arg.prototype)) {
         if (key === 'constructor') continue // 跳過構(gòu)造函數(shù)
         Object.defineProperty(constructor.prototype, key, Object.getOwnPropertyDescriptor(arg.prototype, key))
         }
         }
         }
        }
        
        let c = new C()
        console.log(c.say(), c.hi()) // 1, 2

        多個(gè)裝飾器的應(yīng)用

        裝飾器是可以同時(shí)應(yīng)用多個(gè)的(不然也就失去了最初的意義)。
        用法如下:

        @decorator1
        @decorator2
        class { }

        執(zhí)行的順序?yàn)?code>decorator2 -> decorator1,離class定義最近的先執(zhí)行。
        可以想像成函數(shù)嵌套的形式:

        decorator1(decorator2(class {}))

        @Decorator 在 Class 成員中的使用

        類成員上的 @Decorator 應(yīng)該是應(yīng)用最為廣泛的一處了,函數(shù),屬性,getset訪問器,這幾處都可以認(rèn)為是類成員。
        在TS文檔中被分為了Method DecoratorAccessor DecoratorProperty Decorator,實(shí)際上如出一轍。

        關(guān)于這類裝飾器,會(huì)接收如下三個(gè)參數(shù):

        1. 如果裝飾器掛載于靜態(tài)成員上,則會(huì)返回構(gòu)造函數(shù),如果掛載于實(shí)例成員上則會(huì)返回類的原型

        2. 裝飾器掛載的成員名稱

        3. 成員的描述符,也就是Object.getOwnPropertyDescriptor的返回值

        Property Decorator不會(huì)返回第三個(gè)參數(shù),但是可以自己手動(dòng)獲取
        前提是靜態(tài)成員,而非實(shí)例成員,因?yàn)檠b飾器都是運(yùn)行在類創(chuàng)建時(shí),而實(shí)例成員是在實(shí)例化一個(gè)類的時(shí)候才會(huì)執(zhí)行的,所以沒有辦法獲取對(duì)應(yīng)的descriptor

        靜態(tài)成員與實(shí)例成員在返回值上的區(qū)別

        可以稍微明確一下,靜態(tài)成員與實(shí)例成員的區(qū)別:

        class Model {
         // 實(shí)例成員
         method1 () {}
         method2 = () => {}
        
         // 靜態(tài)成員
         static method3 () {}
         static method4 = () => {}
        }

        method1method2是實(shí)例成員,method1存在于prototype之上,而method2只在實(shí)例化對(duì)象以后才有。
        作為靜態(tài)成員的method3method4,兩者的區(qū)別在于是否可枚舉描述符的設(shè)置,所以可以簡(jiǎn)單地認(rèn)為,上述代碼轉(zhuǎn)換為ES5版本后是這樣子的:

        function Model () {
         // 成員僅在實(shí)例化時(shí)賦值
         this.method2 = function () {}
        }
        
        // 成員被定義在原型鏈上
        Object.defineProperty(Model.prototype, 'method1', {
         value: function () {}, 
         writable: true, 
         enumerable: false, // 設(shè)置不可被枚舉
         configurable: true
        })
        
        // 成員被定義在構(gòu)造函數(shù)上,且是默認(rèn)的可被枚舉
        Model.method4 = function () {}
        
        // 成員被定義在構(gòu)造函數(shù)上
        Object.defineProperty(Model, 'method3', {
         value: function () {}, 
         writable: true, 
         enumerable: false, // 設(shè)置不可被枚舉
         configurable: true
        })

        可以看出,只有method2是在實(shí)例化時(shí)才賦值的,一個(gè)不存在的屬性是不會(huì)有descriptor的,所以這就是為什么TS在針對(duì)Property Decorator不傳遞第三個(gè)參數(shù)的原因,至于為什么靜態(tài)成員也沒有傳遞descriptor,目前沒有找到合理的解釋,但是如果明確的要使用,是可以手動(dòng)獲取的。

        就像上述的示例,我們針對(duì)四個(gè)成員都添加了裝飾器以后,method1method2第一個(gè)參數(shù)就是Model.prototype,而method3method4的第一個(gè)參數(shù)就是Model

        class Model {
         // 實(shí)例成員
         @instance
         method1 () {}
         @instance
         method2 = () => {}
        
         // 靜態(tài)成員
         @static
         static method3 () {}
         @static
         static method4 = () => {}
        }
        
        function instance(target) {
         console.log(target.constructor === Model)
        }
        
        function static(target) {
         console.log(target === Model)
        }

        函數(shù),訪問器,和屬性裝飾器三者之間的區(qū)別

        函數(shù)

        首先是函數(shù),函數(shù)裝飾器的返回值會(huì)默認(rèn)作為屬性的value描述符存在,如果返回值為undefined則會(huì)忽略,使用之前的descriptor引用作為函數(shù)的描述符。
        所以針對(duì)我們最開始的統(tǒng)計(jì)耗時(shí)的邏輯可以這么來做:

        class Model {
         @log1
         getData1() {}
         @log2
         getData2() {}
        }
        
        // 方案一,返回新的value描述符
        function log1(tag, name, descriptor) {
         return {
         ...descriptor,
         value(...args) {
         let start = new Date().valueOf()
         try {
         return descriptor.value.apply(this, args)
         } finally {
         let end = new Date().valueOf()
         console.log(`start: ${start} end: ${end} consume: ${end - start}`)
         }
         }
         }
        }
        
        // 方案二、修改現(xiàn)有描述符
        function log2(tag, name, descriptor) {
         let func = descriptor.value // 先獲取之前的函數(shù)
        
         // 修改對(duì)應(yīng)的value
         descriptor.value = function (...args) {
         let start = new Date().valueOf()
         try {
         return func.apply(this, args)
         } finally {
         let end = new Date().valueOf()
         console.log(`start: ${start} end: ${end} consume: ${end - start}`)
         }
         }
        }

        訪問器

        訪問器就是添加有getset前綴的函數(shù),用于控制屬性的賦值及取值操作,在使用上與函數(shù)沒有什么區(qū)別,甚至在返回值的處理上也沒有什么區(qū)別。
        只不過我們需要按照規(guī)定設(shè)置對(duì)應(yīng)的get或者set描述符罷了:

        class Modal {
         _name = 'Niko'
        
         @prefix
         get name() { return this._name }
        }
        
        function prefix(target, name, descriptor) {
         return {
         ...descriptor,
         get () {
         return `wrap_${this._name}`
         }
         }
        }
        
        console.log(new Modal().name) // wrap_Niko

        屬性

        對(duì)于屬性的裝飾器,是沒有返回descriptor的,并且裝飾器函數(shù)的返回值也會(huì)被忽略掉,如果我們想要修改某一個(gè)靜態(tài)屬性,則需要自己獲取descriptor

        class Modal {
         @prefix
         static name1 = 'Niko'
        }
        
        function prefix(target, name) {
         let descriptor = Object.getOwnPropertyDescriptor(target, name)
        
         Object.defineProperty(target, name, {
         ...descriptor,
         value: `wrap_${descriptor.value}`
         })
        }
        
        console.log(Modal.name1) // wrap_Niko

        對(duì)于一個(gè)實(shí)例的屬性,則沒有直接修改的方案,不過我們可以結(jié)合著一些其他裝飾器來曲線救國(guó)。

        比如,我們有一個(gè)類,會(huì)傳入姓名和年齡作為初始化的參數(shù),然后我們要針對(duì)這兩個(gè)參數(shù)設(shè)置對(duì)應(yīng)的格式校驗(yàn):

        const validateConf = {} // 存儲(chǔ)校驗(yàn)信息
        
        @validator
        class Person {
         @validate('string')
         name
         @validate('number')
         age
        
         constructor(name, age) {
         this.name = name
         this.age = age
         }
        }
        
        function validator(constructor) {
         return class extends constructor {
         constructor(...args) {
         super(...args)
        
         // 遍歷所有的校驗(yàn)信息進(jìn)行驗(yàn)證
         for (let [key, type] of Object.entries(validateConf)) {
         if (typeof this[key] !== type) throw new Error(`${key} must be ${type}`)
         }
         }
         }
        }
        
        function validate(type) {
         return function (target, name, descriptor) {
         // 向全局對(duì)象中傳入要校驗(yàn)的屬性名及類型
         validateConf[name] = type
         }
        }
        
        new Person('Niko', '18') // throw new error: [age must be number]

        首先,在類上邊添加裝飾器@validator,然后在需要校驗(yàn)的兩個(gè)參數(shù)上添加@validate裝飾器,兩個(gè)裝飾器用來向一個(gè)全局對(duì)象傳入信息,來記錄哪些屬性是需要進(jìn)行校驗(yàn)的。
        然后在validator中繼承原有的類對(duì)象,并在實(shí)例化之后遍歷剛才設(shè)置的所有校驗(yàn)信息進(jìn)行驗(yàn)證,如果發(fā)現(xiàn)有類型錯(cuò)誤的,直接拋出異常。
        這個(gè)類型驗(yàn)證的操作對(duì)于原Class來說幾乎是無感知的。

        函數(shù)參數(shù)裝飾器

        最后,還有一個(gè)用于函數(shù)參數(shù)的裝飾器,這個(gè)裝飾器也是像實(shí)例屬性一樣的,沒有辦法單獨(dú)使用,畢竟函數(shù)是在運(yùn)行時(shí)調(diào)用的,而無論是何種裝飾器,都是在聲明類時(shí)(可以認(rèn)為是偽編譯期)調(diào)用的。

        函數(shù)參數(shù)裝飾器會(huì)接收三個(gè)參數(shù):

        1. 類似上述的操作,類的原型或者類的構(gòu)造函數(shù)

        2. 參數(shù)所處的函數(shù)名稱

        3. 參數(shù)在函數(shù)中形參中的位置(函數(shù)簽名中的第幾個(gè)參數(shù))

        一個(gè)簡(jiǎn)單的示例,我們可以結(jié)合著函數(shù)裝飾器來完成對(duì)函數(shù)參數(shù)的類型轉(zhuǎn)換:

        const parseConf = {}
        class Modal {
         @parseFunc
         addOne(@parse('number') num) {
         return num + 1
         }
        }
        
        // 在函數(shù)調(diào)用前執(zhí)行格式化操作
        function parseFunc (target, name, descriptor) {
         return {
         ...descriptor,
         value (...arg) {
         // 獲取格式化配置
         for (let [index, type] of parseConf) {
         switch (type) {
         case 'number': arg[index] = Number(arg[index]) break
         case 'string': arg[index] = String(arg[index]) break
         case 'boolean': arg[index] = String(arg[index]) === 'true' break
         }
        
         return descriptor.value.apply(this, arg)
         }
         }
         }
        }
        
        // 向全局對(duì)象中添加對(duì)應(yīng)的格式化信息
        function parse(type) {
         return function (target, name, index) {
         parseConf[index] = type
         }
        }
        
        console.log(new Modal().addOne('10')) // 11

        使用裝飾器實(shí)現(xiàn)一個(gè)有趣的Koa封裝

        比如在寫Node接口時(shí),可能是用的koa或者express,一般來說可能要處理很多的請(qǐng)求參數(shù),有來自headers的,有來自body的,甚至有來自querycookie的。
        所以很有可能在router的開頭數(shù)行都是這樣的操作:

        router.get('/', async (ctx, next) => {
         let id = ctx.query.id
         let uid = ctx.cookies.get('uid')
         let device = ctx.header['device']
        })

        以及如果我們有大量的接口,可能就會(huì)有大量的router.getrouter.post
        以及如果要針對(duì)模塊進(jìn)行分類,可能還會(huì)有大量的new Router的操作。

        這些代碼都是與業(yè)務(wù)邏輯本身無關(guān)的,所以我們應(yīng)該盡可能的簡(jiǎn)化這些代碼的占比,而使用裝飾器就能夠幫助我們達(dá)到這個(gè)目的。

        裝飾器的準(zhǔn)備

        // 首先,我們要?jiǎng)?chuàng)建幾個(gè)用來存儲(chǔ)信息的全局List
        export const routerList = []
        export const controllerList = []
        export const parseList = []
        export const paramList = []
        
        // 雖說我們要有一個(gè)能夠創(chuàng)建Router實(shí)例的裝飾器
        // 但是并不會(huì)直接去創(chuàng)建,而是在裝飾器執(zhí)行的時(shí)候進(jìn)行一次注冊(cè)
        export function Router(basename = '') {
         return (constrcutor) => {
         routerList.push({
         constrcutor,
         basename
         })
         }
        }
        
        // 然后我們?cè)趧?chuàng)建對(duì)應(yīng)的Get Post請(qǐng)求監(jiān)聽的裝飾器
        // 同樣的,我們并不打算去修改他的任何屬性,只是為了獲取函數(shù)的引用
        export function Method(type) {
         return (path) => (target, name, descriptor) => {
         controllerList.push({
         target,
         type,
         path,
         method: name,
         controller: descriptor.value
         })
         }
        }
        
        // 接下來我們還需要用來格式化參數(shù)的裝飾器
        export function Parse(type) {
         return (target, name, index) => {
         parseList.push({
         target,
         type,
         method: name,
         index
         })
         }
        }
        
        // 以及最后我們要處理的各種參數(shù)的獲取
        export function Param(position) {
         return (key) => (target, name, index) => {
         paramList.push({
         target,
         key,
         position,
         method: name,
         index
         })
         }
        }
        
        export const Body = Param('body')
        export const Header = Param('header')
        export const Cookie = Param('cookie')
        export const Query = Param('query')
        export const Get = Method('get')
        export const Post = Method('post')

        Koa服務(wù)的處理

        上邊是創(chuàng)建了所有需要用到的裝飾器,但是也僅僅是把我們所需要的各種信息存了起來,而怎么利用這些裝飾器則是下一步需要做的事情了:

        const routers = []
        
        // 遍歷所有添加了裝飾器的Class,并創(chuàng)建對(duì)應(yīng)的Router對(duì)象
        routerList.forEach(item => {
         let { basename, constrcutor } = item
         let router = new Router({
         prefix: basename
         })
        
         controllerList
         .filter(i => i.target === constrcutor.prototype)
         .forEach(controller => {
         router[controller.type](controller.path, async (ctx, next) => {
         let args = []
         // 獲取當(dāng)前函數(shù)對(duì)應(yīng)的參數(shù)獲取
         paramList
         .filter( param => param.target === constrcutor.prototype && param.method === controller.method )
         .map(param => {
         let { index, key } = param
         switch (param.position) {
         case 'body': args[index] = ctx.request.body[key] break
         case 'header': args[index] = ctx.headers[key] break
         case 'cookie': args[index] = ctx.cookies.get(key) break
         case 'query': args[index] = ctx.query[key] break
         }
         })
        
         // 獲取當(dāng)前函數(shù)對(duì)應(yīng)的參數(shù)格式化
         parseList
         .filter( parse => parse.target === constrcutor.prototype && parse.method === controller.method )
         .map(parse => {
         let { index } = parse
         switch (parse.type) {
         case 'number': args[index] = Number(args[index]) break
         case 'string': args[index] = String(args[index]) break
         case 'boolean': args[index] = String(args[index]) === 'true' break
         }
         })
        
         // 調(diào)用實(shí)際的函數(shù),處理業(yè)務(wù)邏輯
         let results = controller.controller(...args)
        
         ctx.body = results
         })
         })
        
         routers.push(router.routes())
        })
        
        const app = new Koa()
        
        app.use(bodyParse())
        app.use(compose(routers))
        
        app.listen(12306, () => console.log('server run as http://127.0.0.1:12306'))

        上邊的代碼就已經(jīng)搭建出來了一個(gè)Koa的封裝,以及包含了對(duì)各種裝飾器的處理,接下來就是這些裝飾器的實(shí)際應(yīng)用了:

        import { Router, Get, Query, Parse } from "../decorators"
        
        @Router('')
        export default class {
         @Get('/')
         index (@Parse('number') @Query('id') id: number) {
         return {
         code: 200,
         id,
         type: typeof id
         }
         }
        
         @Post('/detail')
         detail (
         @Parse('number') @Query('id') id: number, 
         @Parse('number') @Body('age') age: number
         ) {
         return {
         code: 200,
         age: age + 1
         }
         }
        }

        很輕易的就實(shí)現(xiàn)了一個(gè)router的創(chuàng)建,路徑、method的處理,包括各種參數(shù)的獲取,類型轉(zhuǎn)換。
        將各種非業(yè)務(wù)邏輯相關(guān)的代碼統(tǒng)統(tǒng)交由裝飾器來做,而函數(shù)本身只負(fù)責(zé)處理自身邏輯即可。
        這里有完整的代碼:GitHub。安裝依賴后npm start即可看到效果。

        這樣開發(fā)帶來的好處就是,讓代碼可讀性變得更高,在函數(shù)中更專注的做自己應(yīng)該做的事情。
        而且裝飾器本身如果名字起的足夠好的好,也是在一定程度上可以當(dāng)作文檔注釋來看待了(Java中有個(gè)類似的玩意兒叫做注解)。

        總結(jié)

        合理利用裝飾器可以極大的提高開發(fā)效率,對(duì)一些非邏輯相關(guān)的代碼進(jìn)行封裝提煉能夠幫助我們快速完成重復(fù)性的工作,節(jié)省時(shí)間。
        但是糖再好吃,也不要吃太多,容易壞牙齒的,同樣的濫用裝飾器也會(huì)使代碼本身邏輯變得撲朔迷離,如果確定一段代碼不會(huì)在其他地方用到,或者一個(gè)函數(shù)的核心邏輯就是這些代碼,那么就沒有必要將它取出來作為一個(gè)裝飾器來存在。

        聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        Javascript裝飾器的用法

        Javascript裝飾器的用法:這篇文章主要介紹了關(guān)于Javascript裝飾器的用法,有著一定的參考價(jià)值,現(xiàn)在分享給大家,有需要的朋友可以參考一下最近新開了一個(gè)Node項(xiàng)目,采用TypeScript來開發(fā),在數(shù)據(jù)庫(kù)及路由管理方面用了不少的裝飾器,發(fā)覺這的確是一個(gè)好東西。 裝飾器是一個(gè)還處于草案
        推薦度:
        標(biāo)簽: 使用 使用方法 的使用
        • 熱門焦點(diǎn)

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 丁香花在线观看免费观看图片| 亚洲av一本岛在线播放| 久久精品亚洲中文字幕无码网站| 亚洲性无码av在线| a级片在线免费看| 亚洲色一色噜一噜噜噜| 欧洲 亚洲 国产图片综合| 免费精品无码AV片在线观看| 亚洲国产精品一区二区第一页免| 亚洲色大18成人网站WWW在线播放| 久视频精品免费观看99| 精品国产亚洲一区二区三区| 香蕉国产在线观看免费| 免费观看的毛片手机视频| 亚洲大片免费观看| 亚洲国产精品嫩草影院久久| 96免费精品视频在线观看| 亚洲成年轻人电影网站www| 最新亚洲成av人免费看| 亚洲香蕉久久一区二区三区四区| 亚洲一区二区三区免费视频| 亚洲精品国产第1页| 免费观看激色视频网站(性色) | 黄页视频在线观看免费| 日韩免费视频网站| 亚洲Aⅴ在线无码播放毛片一线天| 成人性生交视频免费观看| 亚洲 欧洲 视频 伦小说| 亚洲精品亚洲人成人网| 国产色婷婷精品免费视频| 免费人成大片在线观看播放电影| 久久久久亚洲精品男人的天堂 | 在线观看免费大黄网站| mm1313亚洲国产精品无码试看| 国产精品免费看久久久无码| 污污网站免费观看| 亚洲一区二区三区播放在线| 亚洲国产精品成人精品无码区 | a级成人毛片免费图片| 成熟女人特级毛片www免费| 你懂的网址免费国产|