国产99久久精品_欧美日本韩国一区二区_激情小说综合网_欧美一级二级视频_午夜av电影_日本久久精品视频

最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題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關鍵字專題關鍵字專題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
當前位置: 首頁 - 科技 - 知識百科 - 正文

Javascript裝飾器的用法

來源:懂視網 責編:小采 時間:2020-11-27 19:34:08
文檔

Javascript裝飾器的用法

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

因為@符號后邊跟的是一個函數的引用,所以對于mixin的實現,我們可以很輕易的使用閉包來實現:

class A { say() { return 1 } }
class B { hi() { return 2 } }

@mixin(A, B)
class C { }

function mixin(...args) {
 // 調用函數返回裝飾器實際應用的函數
 return function(constructor) {
 for (let arg of args) {
 for (let key of Object.getOwnPropertyNames(arg.prototype)) {
 if (key === 'constructor') continue // 跳過構造函數
 Object.defineProperty(constructor.prototype, key, Object.getOwnPropertyDescriptor(arg.prototype, key))
 }
 }
 }
}

let c = new C()
console.log(c.say(), c.hi()) // 1, 2

多個裝飾器的應用

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

@decorator1
@decorator2
class { }

執行的順序為decorator2 -> decorator1,離class定義最近的先執行。
可以想像成函數嵌套的形式:

decorator1(decorator2(class {}))

@Decorator 在 Class 成員中的使用

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

關于這類裝飾器,會接收如下三個參數:

  1. 如果裝飾器掛載于靜態成員上,則會返回構造函數,如果掛載于實例成員上則會返回類的原型

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

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

Property Decorator不會返回第三個參數,但是可以自己手動獲取
前提是靜態成員,而非實例成員,因為裝飾器都是運行在類創建時,而實例成員是在實例化一個類的時候才會執行的,所以沒有辦法獲取對應的descriptor

靜態成員與實例成員在返回值上的區別

可以稍微明確一下,靜態成員與實例成員的區別:

class Model {
 // 實例成員
 method1 () {}
 method2 = () => {}

 // 靜態成員
 static method3 () {}
 static method4 = () => {}
}

method1method2是實例成員,method1存在于prototype之上,而method2只在實例化對象以后才有。
作為靜態成員的method3method4,兩者的區別在于是否可枚舉描述符的設置,所以可以簡單地認為,上述代碼轉換為ES5版本后是這樣子的:

function Model () {
 // 成員僅在實例化時賦值
 this.method2 = function () {}
}

// 成員被定義在原型鏈上
Object.defineProperty(Model.prototype, 'method1', {
 value: function () {}, 
 writable: true, 
 enumerable: false, // 設置不可被枚舉
 configurable: true
})

// 成員被定義在構造函數上,且是默認的可被枚舉
Model.method4 = function () {}

// 成員被定義在構造函數上
Object.defineProperty(Model, 'method3', {
 value: function () {}, 
 writable: true, 
 enumerable: false, // 設置不可被枚舉
 configurable: true
})

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

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

class Model {
 // 實例成員
 @instance
 method1 () {}
 @instance
 method2 = () => {}

 // 靜態成員
 @static
 static method3 () {}
 @static
 static method4 = () => {}
}

function instance(target) {
 console.log(target.constructor === Model)
}

function static(target) {
 console.log(target === Model)
}

函數,訪問器,和屬性裝飾器三者之間的區別

函數

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

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}`)
 }
 }
 }
}

// 方案二、修改現有描述符
function log2(tag, name, descriptor) {
 let func = descriptor.value // 先獲取之前的函數

 // 修改對應的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前綴的函數,用于控制屬性的賦值及取值操作,在使用上與函數沒有什么區別,甚至在返回值的處理上也沒有什么區別。
只不過我們需要按照規定設置對應的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

屬性

對于屬性的裝飾器,是沒有返回descriptor的,并且裝飾器函數的返回值也會被忽略掉,如果我們想要修改某一個靜態屬性,則需要自己獲取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

對于一個實例的屬性,則沒有直接修改的方案,不過我們可以結合著一些其他裝飾器來曲線救國。

比如,我們有一個類,會傳入姓名和年齡作為初始化的參數,然后我們要針對這兩個參數設置對應的格式校驗:

const validateConf = {} // 存儲校驗信息

@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)

 // 遍歷所有的校驗信息進行驗證
 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) {
 // 向全局對象中傳入要校驗的屬性名及類型
 validateConf[name] = type
 }
}

new Person('Niko', '18') // throw new error: [age must be number]

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

函數參數裝飾器

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

函數參數裝飾器會接收三個參數:

  1. 類似上述的操作,類的原型或者類的構造函數

  2. 參數所處的函數名稱

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

一個簡單的示例,我們可以結合著函數裝飾器來完成對函數參數的類型轉換:

const parseConf = {}
class Modal {
 @parseFunc
 addOne(@parse('number') num) {
 return num + 1
 }
}

// 在函數調用前執行格式化操作
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)
 }
 }
 }
}

// 向全局對象中添加對應的格式化信息
function parse(type) {
 return function (target, name, index) {
 parseConf[index] = type
 }
}

console.log(new Modal().addOne('10')) // 11

使用裝飾器實現一個有趣的Koa封裝

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

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

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

這些代碼都是與業務邏輯本身無關的,所以我們應該盡可能的簡化這些代碼的占比,而使用裝飾器就能夠幫助我們達到這個目的。

裝飾器的準備

// 首先,我們要創建幾個用來存儲信息的全局List
export const routerList = []
export const controllerList = []
export const parseList = []
export const paramList = []

// 雖說我們要有一個能夠創建Router實例的裝飾器
// 但是并不會直接去創建,而是在裝飾器執行的時候進行一次注冊
export function Router(basename = '') {
 return (constrcutor) => {
 routerList.push({
 constrcutor,
 basename
 })
 }
}

// 然后我們在創建對應的Get Post請求監聽的裝飾器
// 同樣的,我們并不打算去修改他的任何屬性,只是為了獲取函數的引用
export function Method(type) {
 return (path) => (target, name, descriptor) => {
 controllerList.push({
 target,
 type,
 path,
 method: name,
 controller: descriptor.value
 })
 }
}

// 接下來我們還需要用來格式化參數的裝飾器
export function Parse(type) {
 return (target, name, index) => {
 parseList.push({
 target,
 type,
 method: name,
 index
 })
 }
}

// 以及最后我們要處理的各種參數的獲取
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服務的處理

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

const routers = []

// 遍歷所有添加了裝飾器的Class,并創建對應的Router對象
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 = []
 // 獲取當前函數對應的參數獲取
 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
 }
 })

 // 獲取當前函數對應的參數格式化
 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
 }
 })

 // 調用實際的函數,處理業務邏輯
 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'))

上邊的代碼就已經搭建出來了一個Koa的封裝,以及包含了對各種裝飾器的處理,接下來就是這些裝飾器的實際應用了:

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
 }
 }
}

很輕易的就實現了一個router的創建,路徑、method的處理,包括各種參數的獲取,類型轉換。
將各種非業務邏輯相關的代碼統統交由裝飾器來做,而函數本身只負責處理自身邏輯即可。
這里有完整的代碼:GitHub。安裝依賴后npm start即可看到效果。

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

總結

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

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

文檔

Javascript裝飾器的用法

Javascript裝飾器的用法:這篇文章主要介紹了關于Javascript裝飾器的用法,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下最近新開了一個Node項目,采用TypeScript來開發,在數據庫及路由管理方面用了不少的裝飾器,發覺這的確是一個好東西。 裝飾器是一個還處于草案
推薦度:
  • 熱門焦點

最新推薦

猜你喜歡

熱門推薦

專題
Top
主站蜘蛛池模板: 久久er | 国产精品高清久久久久久久 | 好看的电影网站亚洲一区 | 欧美视频网站在线观看 | 日韩欧美小视频 | 中文字幕亚洲天堂 | 国产高清在线播放免费观看 | 免费爱爱视频网站 | 亚洲精品高清在线观看 | 中日韩欧美在线观看 | 国产淫语打电话对白在线播放 | 亚洲 欧美 国产另类首页 | 最新色视频 | 国产成人亚洲综合a∨婷婷 国产成人免费在线视频 | 亚洲国产精品一区二区久久 | 国产日韩欧美精品一区二区三区 | 欧美亚洲国产精品第一页 | 亚洲色图欧美色 | 日韩 欧美 亚洲 | 色视频久久 | 一级高清| 欧美在线色图 | 欧美三级在线 | 国产在线观看精品 | 久久天堂网 | 日本免费一区尤物 | 国产高清免费不卡观看 | 日韩激情影院 | 精品一区二区三区四区电影 | 香港一级a毛片在线播放 | 日本特级淫片免费 | 精品国产综合区久久久久99 | 精品国产乱码久久久久久浪潮 | 亚洲午夜一区二区三区 | 在线永久免费观看的毛片 | 国内精品视频一区二区三区 | 国产精品久久久久9999小说 | 欧美色图日韩色图 | 国产精品一区二区三区免费 | 国产欧美另类第一页 | 亚洲一级二级 |