ES7装饰器(Decorator)在Javascript中的使用

栏目: JavaScript · 发布时间: 5年前

内容简介:JS使用的装饰器(Decorator)很像Java的要使用Decorator, 得安装Babel的装饰器的使用非常简单,其本质上就是一个函数。

1. Decorator

JS使用的装饰器(Decorator)很像 Java@annotation ,其目的是可以在运行时改变类或者改变类的属性。

Decorators make it possible to annotate and modify classes and properties at design time.

要使用Decorator, 得安装Babel的 babel-plugin-transform-decorators-legacy 插件用于转译装饰器代码。如果实在VSCode环境下,还得配置 jsconfig.json"compilerOptions.experimentalDecorators": true

2. 使用

装饰器的使用非常简单,其本质上就是一个函数。

1. 装饰类

function log(target, name, descriptor) {
  console.log(target)
  console.log(name)
  console.log(descriptor)
}

@log // 装饰类的装饰器
class Car {
  run() {
    console.log('Car is running')
  }
}

const c1 = new Car()
c1.run()

2. 装饰类方法

function log(target, name, descriptor) {
  console.log(target)
  console.log(name)
  console.log(descriptor)
}

class Car {
  @log // 装饰类方法的装饰器
  run() {
    console.log('Car is running')
  }
}

const c1 = new Car()
c1.run()

3. 参数

装饰器函数有三个参数,其含义在装饰不同属性时表现也不用。

1. 装饰类

在装饰类的时候,第一个参数表示类的函数本身。之前log输出如下:

ES7装饰器(Decorator)在Javascript中的使用

2. 装饰属性

在装饰类方法的时候,第一个参数表示类的原型(prototype), 第二个参数表示方法名, 第三个参数表示被装饰参数的属性。之前log输出如下:

ES7装饰器(Decorator)在Javascript中的使用

第三个参数内有如下属性:

1. configurable - 控制是不是能删、能修改descriptor本身。

2. writable - 控制是不是能修改值。

3. enumerable - 控制是不是能枚举出属性。

4. value - 控制对应的值,方法只是一个value是函数的属性。

5. get和set - 控制访问的读和写逻辑。

4. 动态修改

为什么说Decorator可以在运行时修改类或类的属性?主要是通过装饰器函数的返回值决定的。

1. 装饰类

Decorator的返回值表示新的构造方法。在使用 babel-plugin-transform-decorators-legacy 的情况下, 如果返回值是 null/undefined/false/NaN/''/0/ 则会使用原本的构造函数。

ES7装饰器(Decorator)在Javascript中的使用

使用构造器, 我们可以将小汽车动态改变为自行车。

function log(target, name, descriptor) {
  console.log(target)
  console.log(name)
  console.log(descriptor)
  return class Bike {
    run() {
      console.log('Bike is running')
    }
  }
}

@log
class Car {
  run() {
    console.log('Car is running')
  }
}

const c1 = new Car()
c1.run() // out: Bike is running

2. 装饰成员属性

Decorator函数的返回值表示新的成员属性。如果返回值是 null/undefined/false/NaN/''/0/ 则会返回原本的成员属性。

Decorator在 babel-plugin-transform-decorators-legacy 的情况下对成员属性的动态修改实际上是通过 defineProperty 实现的。

如果被装饰的成员是函数,则第三个参数的value字段表示这个函数本身。基于此,我们可以使用Decorator做一些有意思的事。

1. 日志输出

function log(target, name, descriptor) {
  console.log(Date.now())
}

class Car {
  @log
  run() {
    console.log('Car is running')
  }
}

const c1 = new Car()
c1.run()

2. 函数劫持

function log(target, name, descriptor) {
  const old = descriptor.value
  descriptor.value = function(...arg) { // 注意这里需要保留原this作用域,不能使用箭头函数
    console.log('哈哈哈哈被我劫持啦')
    return old.apply(this, arg)
  }
}

class Car {
  @log
  run() {
    console.log('Car is running')
  }
}

const c1 = new Car()
c1.run()

3. Cache

const memory = () => {
  const cache = Object.create(null)   // 利用闭包的特性,保留一个Object用于缓存函数返回值
  return (target, name, descriptor) => {
    const method = descriptor.value
    descriptor.value = function(...args) {
      const key = args.join('')
      if (cache[key]) {
        return cache[key]
      }
      const ret = method.apply(target, args)
      cache[key] = ret
      return ret
    }
    return descriptor
  }
}

class A {
  @memory() // 实际上是memory函数的返回值作为装饰器
  fib(n) {
    if (n === 1) return 1
    if (n === 2) return 1
    return this.fib(n - 1) + this.fib(n - 2)
  }
}

const a = new A()
console.log(a.fib(100)) // 算的飞快!

5. RequestMapping

利用 Decorator 的特性为 egg 框架制作了 请求参数获取方法

由于使用了babel, 就也顺便增加了egg对于的 import / export 支持。开发可以在dev目录进行,运行 gulp 构建代码至app目录。 npm run dev 的启动目录依旧是app。

Demo

'use strict';

import { Controller } from 'egg'
import GetMapping from '../decorators/GetMapping'

class HomeController extends Controller {
  @GetMapping({
    name: 'id',
    type: Number,
  }, {
    name: 'name',
    type: String,
    defaultValue: 'anonymous'
  }, {
    name: 'right',
    type: Boolean,
  }, {
    name: 'list',
    type: Array,
  })
  async index(id, name, right, list) {
    this.ctx.body = {
      code: 200,
      content: { id, name, right, list },
    };
  }
}

export default HomeController;

1. 实现思路

  1. 对Controller函数进行劫持
  2. 根据装饰器传入的参数判断从ctx.query/ctx.queries内获取相应字段
  3. 对获取到的字段进行判断,根据defaultValue设置相应默认值

其中最需要注意的就是this指向的问题。定义新的descriptor.value时必须用function定义而不能使用箭头函数,否则会丢失this指向从而导致无法获取到相应作用域。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数值方法和MATLAB实现与应用

数值方法和MATLAB实现与应用

拉克唐瓦尔德 / 机械工业出版社 / 2004-9 / 59.00元

本书是关于数值方法和MATLAB的介绍,是针对高等院校理工科专业学生编写的教材。数值方法可以用来生成其他方法无法求解的问题的近似解。本书的主要目的是为应用计算打下坚实的基础,由简单到复杂讲述了标准数值方法在实际问题中的实现和应用。本书通篇使用良好的编程习惯向读者展示了如何清楚地表达计算思想及编制文档。书中通过给读者提供大量的可直接运行的代码库以及讲解MARLAB工具箱中内置函数使用的数量方法,帮助......一起来看看 《数值方法和MATLAB实现与应用》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试