初探ES7 Decorator

时间:2022-06-06
本文章向大家介绍初探ES7 Decorator,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

装饰器

装饰器是 ES7 新有的特性,它允许我们使用简洁的方式,为已有的类、类的方法、类的属性 添加有趣的修饰。 可使用如下:

// deco.js
// 假设已实现 装饰器 studentDecorator、readonly、shouldITellYou
@studentDecorator
class Person{

  @readonly
  name = 'carvenzhang';

  @shouldITellYou
  getInfo(){
    //...
  }
}

目前,node 还不支持 decorator,但是,感谢 babel,它提供了转译支持。 我们可以运行使用如下:

# 先安装 babel-cli 及 babel-plugin-transform-decorators-legacy
babel --plugins transform-decorators-legacy deco.js > deco.es5.js && node deco.es5.js

实现 Decorator

那么,装饰器要怎么实现了,什么场景下需要用到decorator呢。 装饰器比较使用于,在完整的系统功能上,提供辅助能力。比如 记录日志查询权限。 目前由一个很好的集成装饰器,可以提供学习:core-decorators

我们可以通过编写一个权限审核的Decorator,达到学习decorator的目的。

编写一个原始类

我们编写一个很简单的class,模拟一些操作。

// deco.js
class DBAct{
  constructor(options){
    this._options = Object.assign({
      auths:[]
    }, options);
  }
  add(){
    console.log('db add');
  }
  delete(){
    console.log('db delete');
  }
  update(){
    console.log('db update');
  }
  select(){
    console.log('db select');
  }
}

这是一个数据库操作集合,可以通过调用方法, 实现增删改查。

我们通过编写decorator,为其提供操作数据库前的权限审核能力。

实现方法型decorator

首先,我们实现一个 decoraotr,为每个方法提供一个权限检查的能力。 供给方法使用的 decorator 有三个参数,targetnamedescriptor,与Object.defineProperty()的参数一一对应。

target: 类的原型对象,比如 DBAct.prototype name: 要修饰的方法的属性名,比如 add descriptor: 该属性的描述对象,包含 {configurable, enumerable, value, writable } 等值,其中value即实际修饰的函数。

实现如下:

function authMethodDecorator(auth){
  //函数封装层,返回decorator
  return function(target, name, descriptor){
    // 获取原函数
    const method = descriptor.value;
    // 重写函数
    descriptor.value = function(...args) {
      // 判断是否有该权限
      if(this._options.auths.includes(auth)){
        // 继续调用原函数
        return method.apply(this, args);
      } else {
        // 无权限
        console.log(`no permission to exec ${name}`)
      }
    }
    return descriptor;
  }
}

整合起来调用如下:

// deco.js
class DBAct{
  constructor(options){
    this._options = Object.assign({
      auth:{}
    }, options);
  }

  // 只有在有 add 权限的情况下,才能进行 add 操作。
  @authMethodDecorator('add')
  add(){
    console.log('db add');
  }

  // 只有在有 delete 权限的情况下,才能进行 delete 操作。
  @authMethodDecorator('delete')
  delete(){
    console.log('db delete');
  }
  update(){
    console.log('db update');
  }
  select(){
    console.log('db select');
  }
}

// 实例化
const user1 = new DBAct({
  auths:['add'] // user1 仅有 add 权限
});
r1.add();
r1.delete();

// 关于方法的decorator
function authMethodDecorator(auth){
  //..
}

最终通过babel转译,node调用,得到以下结果

# exec
babel --plugins transform-decorators-legacy deco.js > deco.es5.js && node deco.es5.js

# echo
db add
no permission to exec delete

实现类的Decorator

我们也可以实现一个Decorator,为整个class的每一个方法都统一做权限检查。 类的decorator仅有一个参数:

target: 调用的类本身,比如 DBAct

实现如下:

function authClassDecorator(auth){
   //函数封装层,返回decorator
  return function(target){
    // 获取 class 上所有的描述对象
    const descs = Object.getOwnPropertyDescriptors(target.prototype);
    // 获取 class 上所有的属性名,此方法不兼容 Symbol,实际解决方案参考 core-decorator
    const keys = Object.keys(descs);
    //循环处理每一个方法
    for (let i = 0, l = keys.length; i < l; i++) {
      const name = keys[i];
      const desc = descs[name];

      // 不处理 非函数 和 构造函数
      if (typeof desc.value !== 'function' || name === 'constructor') {
        continue;
      }
      // 为每个方法分别调用一次 authMethodDecorator,重写给 target.prototype
      Object.defineProperty(target.prototype, name, authMethodDecorator(auth)(target.prototype, name, desc));
    }
  }
}

整合起来调用如下:

// deco.js
//所有方法都需要connect权限才能执行
@authClassDecorator('connect')
class DBAct{
  constructor(options){
    this._options = Object.assign({
      auth:{}
    }, options);
  }
  add(){
    console.log('db add');
  }
  delete(){
    console.log('db delete');
  }
  update(){
    console.log('db update');
  }
  select(){
    console.log('db select');
  }
}

// 实例化
const user1 = new DBAct({
  auths:['add'] // user1 仅有 add 权限
});
r1.add();
r1.delete();

// 关于方法的decorator
function authMethodDecorator(auth){
  //..
}
// 关于类的decorator
function authClassDecorator(auth){
  //..
}

最终通过babel转译,node调用,得到以下结果

# exec
babel --plugins transform-decorators-legacy deco.js > deco.es5.js && node deco.es5.js

# echo
no permission to exec add
no permission to exec delete

decorator整合

那么,我们能不能实现一个decoraotor,既能给calss用,也能给method用呢? 可以的:

@authDecorator('connect')
class DBAct{
  constructor(options){
    this._options = Object.assign({
      auths:[]
    }, options);
  }

  @authDecorator('add')
  add(){
    console.log('db add');
  }

  @authDecorator('delete')
  delete(){
    console.log('db delete');
  }
  update(){
    console.log('db update');
  }
  select(){
    console.log('db select');
  }
}

const user1 = new DBAct({
  auths:['add', 'connect'] // user1 仅有 add 权限
});
user1.add();
user1.delete();


function authMethodDecorator(auth, target, name, descriptor){
  // 获取原函数
  const method = descriptor.value;
  // 重写函数
  descriptor.value = function(...args) {
    // 判断是否有该权限
    // if(this === target){
    //   return method.apply(target, args)
    // }
    if(this._options.auths.includes(auth)){
      // 继续调用原函数
      return method.apply(this, args);
    } else {
      // 无权限
      console.log(`no permission to exec ${name}`)
    }
  }
  return descriptor;
}

function authClassDecorator(auth, target){
  // 获取 class 上所有的描述对象
  const descs = Object.getOwnPropertyDescriptors(target.prototype);
  // 获取 class 上所有的属性名,此方法不兼容 Symbol,实际解决方案参考 core-decorator
  const keys = Object.keys(descs);
  //循环处理每一个方法
  for (let i = 0, l = keys.length; i < l; i++) {
    const name = keys[i];
    const desc = descs[name];

    // 不处理 非函数 和 构造函数
    if (typeof desc.value !== 'function' || name === 'constructor') {
      continue;
    }
    // 为每个方法分别调用一次 authMethodDecorator,重写给 target.prototype
    Object.defineProperty(target.prototype, name, authMethodDecorator(auth, target.prototype, name, desc));
  }
}

// 通过参数个数判断是 调用方式 method 还是 class
function authDecorator(auth){
  return function handle(...args) {
    if (args.length === 1) {
      return authClassDecorator(auth, ...args);
    }
    return authMethodDecorator(auth, ...args);
  };
}

最终通过babel转译,node调用,得到以下结果

# exec
babel --plugins transform-decorators-legacy deco.js > deco.es5.js && node deco.es5.js

# echo
db add
no permission to exec delete

这样,一个通用的decorator就出来了。

迁移

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=jwpzso9zymr