前言:

装饰器Decorator ECMAScript中已经提案,但是目前还没有定案;在TypeScript中已经将其实现,但是这仍是一项正在试验中的特性,如果想要使用装饰器,需要在tsconfig.json中将experimentalDecorators属性,将其设置为true。

1.概念

1.1定义

装饰器是一种新的声明,它可以作用于类声明 、方法 、访问器 、属性 以及参数 上。装饰器的使用采用@符号加一个函数名称,例如@testDecorator,其中,这个testDecorator必须是一个函数或者 return一个函数 ,这个函数在运行的时候被调用,被装饰的声明作为参数会自动传入。

值得注意的是 ,装饰器要紧挨着要修饰的内容的前面 ,而且所有的装饰器不能用在声明文件.d.ts.中,和任何外部上下文中(比如declare)。

装饰器的定义以及使用如下所示:

// 定义一个函数作为装饰器函数使用
function testDecorator() {}

// 通过@符号使用装饰器
@testDecorator

1.2装饰器工厂

所谓的装饰器工厂也是一个函数,与普通的装饰器函数不同的是它的返回值是一个函数,返回的函数作为装饰器调用的函数。如果使用装饰器工厂,可以在使用的时候根据当前的使用情况,传递不同的参数,但是在使用的时候,就需要加上函数调用。

示例代码如下:

// 装饰器工厂,返回值是一个函数
function testDecorator() {
    return function() {}
}

// 通过@符号   函数调用的方式使用装饰器
@testDecorator()

1.3装饰器组合使用

装饰器是可以组合使用的,也就是说可以对用一个目标,引用多个装饰器,

示例代码如下所示:

// 定义两个装饰器函数
function setName() {}
function setAge() {}

// 使用装饰器
@setName
@setAge
class Person {}

如果使用多个装饰器,装饰器的执行是有顺序的,执行顺序如下:

如果使用的普通的装饰器函数的话,执行顺序是从下往上执行的,

示例代码如下:

function setName(constructor: any) {
  console.log('setName', constructor)
}
function setAge(constructor: any) {
  console.log('setAge', constructor)
}
@setName
@setAge
class Person {}
/* 执行结果如下:
setAge [Function: Person]
setName [Function: Person]
*/


如果是装饰器工厂的,它的执行顺序是先从上到下依次执行工厂函数,然后从下往上依次执行工厂函数return的函数。示例代码如下

function setName() {
  console.log('get setName')
  return function (constructor: any) {
    console.log('setName', constructor)
  }
}
function setAge() {
  console.log('get setAge')
  return function (constructor: any) {
    console.log('setAge', constructor)
  }
}
@setName()
@setAge()
class Person {}
/* 执行结果如下:
get setName
get setAge
setAge [Function: Person]
setName [Function: Person]
*/

1.4装饰器求值

类的定义中不同声明上的装饰器将按以下规定的顺序引用:

  • 参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个实例成员;
  • 参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个静态成员;
  • 参数装饰器应用到构造函数;
  • 类装饰器应用到类。

2.类装饰器

类装饰器 在类声明之前使用,必须紧挨着需要装饰的内容,类装饰器应用于类的声明。

类装饰器表达式会在运行时当做函数被调用,它有一个参数,就是这个类的构造函数。

示例代码如下:

let sign = null
function setName() {
  return function (constructor: Function) {
    sign = constructor
  }
}
@setName()
class Info {
  constructor() {}
}
console.log(sign === Info) // true
console.log(sign === Info.prototype.constructor) // true

如上代码可以知道类Info的原型对象的constructor属性指向的其实就是Info本身。

我们还可以通过装饰器来修改类的原型对象和构造函数,示例代码如下:

// * 通过装饰器 修改原型对象与构造函数
function addName(constructor: { new (): any }) {
  constructor.prototype.name = '一碗周'
}
@addName
class Person {}
const person = new Person()
console.log(person.name) // error 类型“A”上不存在属性“name”

在上面的代码中,我们通过addName修饰符在类Person的原型上添加一个name属性,这样使得通过Person类实例化的对象,都可以访问name这个属性,但是实际上并不是这样的,这里已经抛出一个异常,想要解决这个问题,可以通过类型断言的方式,也可以通过定义一个同名接口,通过声明合并的方式解决这个问题。

示例代码如下:

function addName(constructor: { new (): any }) {
  constructor.prototype.name = '一碗周'
}
@addName
class Person {}
const person = new Person()
// 1. 类型断言
// console.log((person as any).name) // 一碗周

// 2. 定义同名接口,声明合并
interface Person {
  name: string
}

console.log(person.name) // 一碗周

而且我们还可以通过装饰器重载构造函数,示例代码如下:

// * 重载构造函数
function classDecorator<T extends { new (...args: any[]): {} }>(
  constructor: T,
) {
  return class extends constructor {
    name = '一碗周'
    hobby = 'coding'
  }
}
@classDecorator
class Person {
  age = 18
  name: string
  constructor(name: string) {
    this.name = name
  }
}
const person = new Person('一碗周')
console.log(person)
/* 执行结果如下:
{
  age: 18,
  name: '一碗周',
  hobby: 'coding',
}
*/

我们还可以通过装饰器工厂的方式来传递参数,示例代码如下:

// 定义一个装饰器工厂
function classDecorator(_name: string) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
      name = _name
      hobby = 'coding'
    }
  }
}
@classDecorator('一碗周')
class Person {
  age = 18
  name: string
  constructor(name: string) {
    this.name = name
  }
}
const person = new Person('一碗粥')
console.log(person)
/* 执行结果如下:
{
  age: 18,
  name: '一碗周',
  hobby: 'coding',
}
*/

3.方法装饰器

方法装饰器用来处理类中的方法,它可以处理方法的属性描述符(关于什么是属性描述符,请参考Object.defineProperty() ),也可以处理方法定义。方法装饰器在运行时也是被当做函数调用,其包含三个参数,

具体如下所示:

对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。

成员的名字。

成员的属性描述符 。

值得注意的是如果代码输出目标版本小于ES5,属性描述符 将会是undefined

如下代码通过装饰器工厂定义了一个简单的方法装饰器,示例代码如下:

// 装饰器工厂
function enumerable(bool: boolean) {
  /**
   * 方法装饰器接受三个参数:
   * 1. target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
   * 2. propertyName:成员的名字
   * 3. descriptor:属性描述符,其类型为 PropertyDescriptor
   */
  return function (
    target: any,
    propertyName: string,
    descriptor: PropertyDescriptor,
  ) {
    // 根据传入的bool决定该方法是否可枚举
    descriptor.enumerable = bool
  }
}
class Info {
  constructor(public name: string) {}
  @enumerable(false)
  getName() {
    return this.name
  }
}
const info = new Info('一碗周')
// 如果直接打印,该对象中不包含 getName() 方法,因为该方法是不可枚举的。
console.log(info) // { name: '一碗周' }
// 但是可以调用该方法
console.log(info.getName()) // 一碗周

在上面的代码中,我们直接通过装饰器对类中的方法的属性描述符进行了修改。

如果方法装饰器返回一个值,那么会用这个值作为方法的属性描述符对象,示例代码如下:

// 装饰器工厂
function enumerable(bool: boolean) {
  return function (
    target: any,
    propertyName: string,
    descriptor: PropertyDescriptor,
  ) {
    return {
      value: function () {
        return 'Error: name is undefined'
      },
      enumerable: bool,
    }
  }
}
class Info {
  constructor(public name: string) {}
  @enumerable(false)
  getName() {
    return this.name
  }
}
const info = new Info('一碗周')
console.log(info) // { name: '一碗周' }
console.log(info.getName()) // Error: name is undefined

在上面的代码中,我们的方法装饰器中返回了一个对象,该对象的value属性修改了方法的定义,所以最终看到的结果为Error: name is undefined

4.访问器装饰器

访问器装饰器就是之前所学习的setget方法,一个在设置属性值的时候触发,一个在获取属性值的时候触发。

访问器装饰器同样也接受三个参数,与方法装饰器一样,这里不做赘述了,

示例代码如下:

function enumerable(bool: boolean) {
  return function (
    target: any,
    propertyName: string,
    descriptor: PropertyDescriptor,
  ) {
    descriptor.enumerable = bool
  }
}
class Info {
  private _name: string
  constructor(name: string) {
    this._name = name
  }
  @enumerable(false)
  get name() {
    return this._name
  }
  set name(name) {
    this._name = name
  }
}

值得注意的是,在TypeScript不允许同时装饰一个成员的getset访问器。

5.属性装饰器

属性装饰器声明在属性声明之前,它有两个参数,如下所示:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字。

示例代码如下:

function printPropertyName(target: any, propertyName: string) {
  console.log(propertyName)
}
class Info {
  @printPropertyName
  name: string
  @printPropertyName
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}
new Info('一碗周', 18)

执行结果如下:

name
age

6.参数装饰器

参数装饰器具有三个参数,具体如下:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字。
  • 参数在函数参数列表中的索引。

参数装饰器的作用是用于监视一个方法的参数是否被传入,参数装饰器的返回值会被忽略。

示例代码如下:

function required(target: any, propertyName: string, index: number) {
  console.log(`修饰的是${propertyName}的第${index   1}个参数`)
}
class Info {
  name: string = '一碗周'
  age: number = 18
  getInfo(prefix: string, @required infoType: string): any {
    return prefix   ' '   this[infoType]
  }
}
interface Info {
  [key: string]: string | number | Function
}
const info = new Info()
info.getInfo('', 'age') // 修饰的是getInfo的第2个参数


这里我们在getInfo方法的第二个参数之前使用参数装饰器,从而可以在装饰器中获取到一些信息。

到此这篇关于TypeScript 装饰器定义的文章就介绍到这了,更多相关TypeScript 装饰器内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

TypeScript 装饰器定义的更多相关文章

  1. 使用typescript类型实现ThreeSum

    这篇文章主要介绍了使用typescript类型实现ThreeSum,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以一下,希望对你学习又是帮助

  2.  typeScript入门基础介绍

    这篇文章主要介绍了 typeScript入门基础,TypeScript 是由微软开发的开源、跨平台的编程语言,是 javaScript 的超集,最终被编译为 javaScript代码。常常被简称为TS支持JS、ES语法,下文将继续其他基础介绍,需要的朋友可以参考一下

  3. typescript返回值类型和参数类型的具体使用

    本文主要介绍了typescript返回值类型和参数类型的具体使用文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  4. Vue3 携手 TypeScript 搭建完整项目结构

    TypeScript 是JS的一个超级,主要提供了类型系统和对ES6的支持,使用 TypeScript 可以增加代码的可读性和可维护性,在 react 和 vue 社区中也越来越多人开始使用TypeScript,这篇文章主要介绍了Vue3 携手 TypeScript 搭建完整项目结构,需要的朋友可以参考下

  5. Java如何定义Long类型

    这篇文章主要介绍了Java如何定义Long类型,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  6. 关于TypeScript开发的6六个实用小技巧分享

    TypeScript是Javascrip t超集,支持静态类型检测,可以在编译期提前暴露问题,节省debug时间,下面这篇文章主要给大家介绍了关于TypeScript开发的6六个实用小技巧,需要的朋友可以参考下

  7. typescript+react实现移动端和PC端简单拖拽效果

    这篇文章主要为大家详细介绍了typescript+react实现移动端和PC端简单拖拽效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  8. Python基础globlal nonlocal和闭包函数装饰器语法糖

    这篇文章主要为大家介绍了Python基础globlal nonlocal和闭包函数装饰器语法糖示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  9. Vue3 TypeScript 实现useRequest详情

    本文介绍了Vue3 TypeScript实现useRequest详情,useRequest可能是目前社区中最强大,最接地气的请求类 Hooks了。可以覆盖99%的网络请求场景,无论是读还是写,无论是普通请求还是分页请求,无论是缓存还是防抖节流,通通都能支持,关于其介绍需要的小伙伴可以参考一下

  10. iOS如何定义名为任意的变量详解

    这篇文章主要给大家介绍了关于iOS如何定义名为任意的变量的相关资料,文中通过示例代码介绍的非常详细,对各位iOS开发者们具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部