簡單瞭解一下ES6的修飾器

閒言

一切都要從公司裏的一位老哥給我看的一段代碼提及。。。javascript

@controller('/user')

@auth
@post('/login')
async userLogin = (name, pass) => {
    @required
    // ...
}

如下爲對話:html

我:這不是修飾器嗎(由於以前看到過@這個東西)

老哥:還不錯嘛,知道是修飾器,那你知道這一段想表達什麼意思嗎java

我:這是路由?(一臉懵逼,可是看到了/user和post還有/login,內心想難道這是路由)api

老哥:穩!async

我:震驚了,還可以這樣寫路由。不行,回去我要好好看看這個破@函數

由此開始了修飾器的學習~~~
嚶嚶嚶~~post

初識Decorator

首先說明,修飾器在JavaScript中還處於提議階段,目前還不可以被大部分環境支持,而且以後還有可能會改變。若是想要使用該特性請用Babel進行轉碼或者使用JavaScript的超集TypeScript

在ES6中增長了類的相關定義和操做(好比class和extends),這樣方便了咱們單個類的操做,可是當咱們想要在多個不一樣類之間共享、複用一些方法的時候,會發現變得不那麼優雅,因此decorator被提了出來。學習

小小的demo:以@做爲標識符,既能夠做用於類,也能夠做用於類屬性ui

@decorator
class Cat {}

class Dog {
    @decorator
    run() {}
}

修飾器的使用

既然decorator與類相關,咱們先了解一下類(這裏只是簡單介紹,想要詳細瞭解的,請自行查閱相關資料,推薦阮一峯的ES6入門)this

class Cat {
    constructor(name) {
        this.name = name
    }
    say () {
        console.log("miao ~")
    }
}

這實際上是一個語法糖,具體的實現是經過Object.defineProperty()方法來操做的,該方法語法以下:

Object.defineProperty(obj, prop, descriptor)

-> obj: 要在其上定義屬性的對象
-> prop: 要定義或修改的屬性的名稱
-> descriptor:要被定義或修改的屬性描述符

返回:傳遞給該方法的對象(即obj)

因此上面那個Cat的代碼實際上在執行時是這樣的:

function Cat() {}
Object.defineProperty(Cat.prototype, "say", {
    value: function() {console.log("miao ~")}, // 該屬性對應的值
    enumerable: false, // 爲true時,纔可以出如今對象的枚舉屬性中
    configurable: true, // 爲true時,該屬性描述符纔可以被改變
    writable: true // 爲true時,value才能被賦值運算符改變
}) // 返回Cat.prototype

做用於類的修飾器

function isAnimal(target) { 
    target.isAnimal = true
    return target // 返回的是傳遞給該函數的對象
}

@isAnimal
class Cat {
    // ...
}
console.log(Cat.isAnimal) // true

上面的代碼基本等同於:

Cat = isAnimal(function Cat() {})

做用於類屬性的修飾器

function readonly(target, name, descriptor) { 
    descriptor.writable = false
    return descriptor
}

class Cat {
    @readonly 
    say () {
        console.log("miao ~")
    }
}

let kitty = new Cat()
kitty.say = function () {
    console.log("wow ~")
}

kitty.say() // miao ~

經過將descriptor屬性描述符的writable設置爲false,使得say方法只讀,後面對它進行的賦值操做不會生效,調用的依舊是以前的方法。

有木有以爲readonly()方法的參數似曾相識?它和上文介紹ES6中的類中提到的Object.defineProperty()是同樣的。

其實修飾器在做用於屬性的時候,其實是經過Object.defineProperty進行擴展和封裝的。因此上面的代碼其實是這樣的:

let descriptor = {
    value: function() {
        console.log("miao ~")
    },
    enumerable: false,
    configurable: true,
    writable: true
}

descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor
Object.defineProperty(Cat.prototype, "say", descriptor)

當修飾器做用於類時,咱們操做的對象是類自己,當修飾器做用於類屬性時,咱們操做的對象既不是類自己也不是類屬性,而是它的描述符(descriptor)。

固然了,你也能夠直接在target上進行擴展和封裝。

function fast(target, name, descriptor) {
    target.speed = 20
    let run = descriptor.value()
    descriptor.value = function() {
        run()
        console.log(`speed ${this.speed}`)
    }
    return descriptor
}

class Rabbit {
    @fast
    run() {
        console.log("running~")
    }
}

let bunny = new Rabbit()

bunny.run() 
// running ~
// speed 20
console.log(bunny.speed) // 20

回到文章開始

讓咱們回到文章開始講到的代碼,它怎麼閱讀呢

@controller("/api/user")

export class userController {
    @post("/add")
    @required({
        body: ["telephone", 'key1']
    })
    async addUser(ctx, next) {}
    
    @get("/userlist")
    async userList(ctx, next) {}
}

讓咱們先看@controller("/api/user")

const symbolPrefix = Symbol('prefix')
export const controller = path => target => (target.prototype[symbolPrefix] = path)

做用是將/api/user做爲prefixPath,此時target爲userController {},在該target的原型上設置path

再接着看@post("/add")

// 如下代碼省略了部分細節
...
// export const post = path => (target, name, descriptor) => {}

routerMap.set({
    target: target,
    ...conf
}, target[name]) // name爲addUser
for(let [conf, func] of routerMap) {
    // conf爲{target, path} 該target爲userController {},path爲@post()傳遞進來的參數
    let prefixPath = conf.target[symbolPrefix] // 爲 /api/user
    let routePath = prefix + path // 爲 /api/user/add
}
// 獲得了api路徑(routePath),也獲得了該api路徑所執行的方法(func)
// get同理
...

原理基本上都是相似的,處理修飾器傳遞的參數,獲得本身想要的結果。

額,經歷過上面的知識瞭解,應該能大概夠理解這段代碼了吧~

小結

修飾器容許你在類和方法定義的時候去註釋或者修改它。修飾器是一個做用於函數的表達式,它接收三個參數 target、 name 和 descriptor , 而後可選性的返回被裝飾以後的 descriptor 對象。

你也能夠疊加使用,就像這樣

@post("/add")
@required({
    body: ["telephone", 'key1']
})

async xxx {}

參考:

相關文章
相關標籤/搜索