资讯专栏INFORMATION COLUMN

简单了解一下ES6的修饰器

LancerComet / 769人阅读

摘要:额,经历过上面的知识了解,应该能大概够理解这段代码了吧小结修饰器允许你在类和方法定义的时候去注释或者修改它。

闲言

一切都要从公司里的一位老哥给我看的一段代码说起。。。

@controller("/user")

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

以下为对话:

我:这不是修饰器吗(因为之前看到过@这个东西)

老哥:还不错嘛,知道是修饰器,那你知道这一段想表达什么意思吗

我:这是路由?(一脸懵逼,但是看到了/user和post还有/login,心里想难道这是路由)

老哥:稳!

我:震惊了,还能够这样写路由。不行,回去我要好好看看这个破@

由此开始了修饰器的学习~~~
嘤嘤嘤~~

初识Decorator
首先说明,修饰器在JavaScript中还处于提议阶段,目前还不能够被大部分环境支持,并且之后还有可能会改变。如果想要使用该特性请用Babel进行转码或者使用JavaScript的超集TypeScript

在ES6中增加了类的相关定义和操作(比如class和extends),这样方便了我们单个类的操作,但是当我们想要在多个不同类之间共享、复用一些方法的时候,会发现变得不那么优雅,所以decorator被提了出来。

小小的demo:以@作为标识符,既可以作用于类,也可以作用于类属性

@decorator
class Cat {}

class Dog {
    @decorator
    run() {}
}
修饰器的使用

既然decorator与类相关,我们先了解一下类(这里只是简单介绍,想要详细了解的,请自行查阅相关资料,推荐阮一峰的ES6入门)

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 {}
参考:

https://aotu.io/notes/2016/10/24/decorator/index.html

http://taobaofed.org/blog/2015/11/16/es7-decorator/

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94340.html

相关文章

  • 工作中常用es6+特性

    摘要:结合工作中使用情况,简单对进行一些复习总结,包括常用的语法,等,以及短时间内要上手需要重点学习的知识点不同工作环境可能有一些差别,主要参考链接是阮一峰的博客以及外文博客阮老师大部分文章是直接翻译的这个博客简介先说一下,是一个标准化组织,他们 结合工作中使用情况,简单对es6进行一些复习总结,包括常用的语法,api等,以及短时间内要上手需要重点学习的知识点(不同工作环境可能有一些差别),...

    xcold 评论0 收藏0
  • ES7 Decorators(修饰

    ES6 Decorators(修饰器) 修饰器(Decorator)是一个函数,用来修改类的行为。这是ES7的一个提案,目前Babel转码器已经支持 我们在游戏大型项目种经常会用到的方法,现在es6直接支持 想要使用Decorator的话需要我们配置一下文件夹,配置一下环境 npm install babel-plugin-transform-decorators-legacy --save-de...

    张汉庆 评论0 收藏0
  • ES6学习 第五章 正则扩展

    摘要:前言本章介绍正则的扩展。属性属性表明正则表达式带有标志。行终止符所谓行终止符,就是该字符表示一行的终结。比如,只匹配不在美元符号后面的数字,要写成。前言本章介绍正则的扩展。有些不常用的知识了解即可。本章原文链接:正则的扩展RegExp 构造函数从 ES6 开始,如果RegExp构造函数第一个参数是一个正则对象,并且第二个标志存在且为标志参数,将不再抛出 TypeError ,将使用这些参数创...

    番茄西红柿 评论0 收藏2637
  • ES6简单总结(搭配简单讲解和小案例)

    摘要:方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的方法类似。不可以当作构造函数,也就是说,不可以使用命令,否则会抛出一个错误。本身是一个构造函数,用来生成数据结构。返回一个布尔值,表示该值是否为的成员。清除所有成员,没有返回值。 在学习es6的过程中,为了方便自己复习,以及查看,对api做了一个极简用例介绍。如有错误多多指正。 一 let和const 1.let (1)一个大...

    joyqi 评论0 收藏0
  • ES6学习笔记4-Proxy、Reflect、Decorator、Module

    摘要:拦截实例作为构造函数调用的操作,比如。方法等同于,这提供了一种不使用,来调用构造函数的方法。方法对应,返回一个布尔值,表示当前对象是否可扩展。这是的一个提案,目前转码器已经支持。别名或修饰器在控制台显示一条警告,表示该方法将废除。 Proxy Proxy 这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器,即用自己的定义覆盖了语言的原始定义。ES6 原生提供 Proxy...

    lushan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<