ES7 装饰器

阅读能解决问题-:
1)装饰器有什么用,主要功能?
2)装饰器?减少引入,减少代码,可以扩展,不需要改原有方法的代码位置
3)放置位置,可以是类、类成员(方法/属性)
4)执行顺序
5)目前项目代码可以加装饰器吗?
6)是不是一定要用类

目录:
1、前言
2、ES7 装饰器
3、应用
4、装饰器只能用于类上吗?为什么不能用于函数?—因为存在函数提升,类不会
5、第三方 core-decorators.js,提供一下装饰器
6、已有项目添加装饰器

1、前言

装饰器是 ES7 中的一个提案,是一种与类(class)相关的语法,用来注释或修改类和类方法。装饰器在 Python 和 Java 等语言中也被大量使用。装饰器是实现 AOP(面向切面)编程的一种重要方式。
装饰器类:

@frozen
class Foo {
@configurable(false)
method() {}}

@frozen 和 @configurable 就是我们说的装饰器。
可以看出是通过@来使用装饰器。一共用了两种:一个用在类上,一个用在方法上。
装饰器属性:这个 @readonly 可以将 count 属性设置为只读。可以看出来,装饰器大大提高了代码的简洁性和可读性。
class Person {
@readonly count = 0;
}

2、ES7 装饰器

2.1 python中的装饰器

def auth(func):
    def inner(request,*args,**kwargs):
        v = request.COOKIES.get('user')
        if not v:
            return redirect('/login')
        return func(request, *args,**kwargs)
    return inner


@auth
def index(request):
    v = request.COOKIES.get("user")
    return render(request,"index.html",{"current_user":v})

这个 auth 装饰器是通过检查 cookie 来判断用户是否登录的。auth 函数是一个高阶函数,它接收了一个 func 函数作为参数,返回了一个新的 inner 函数。
在 inner 函数中进行 cookie 的检查,由此来判断是跳回登录页面还是继续执行 func 函数。
在所有需要权限验证的函数上,都可以使用这个 auth 装饰器,很简洁明了且无侵入。

2.2 javascript装饰器

JavaScript 中的装饰器和 Python 的装饰器类似,依赖于 Object.defineProperty,一般是用来装饰类、类属性、类方法。
使用装饰器可以做到不直接修改代码,就实现某些功能,做到真正的面向切面编程。这在一定程度上和 Proxy 很相似,但使用起来比 Proxy 会更加简洁。

2.3 类装饰器-接受一个目标类作为参数

1)添加静态属性

const decoratorClass = (targetClass) => {
    targetClass.test = '123'
}
@decoratorClass
class Test {}
Test.test; // '123'

2)修改原型,给实例添加新属性

const withSpeak = (targetClass) => {
    const prototype = targetClass.prototype;
    prototype.speak = function() {
        console.log('I can speak ', this.language);
    }
}
@withSpeak
class Student {
    constructor(language) {
        this.language = language;
    }
}
const student1 = new Student('Chinese');
const student2 = new Student('English');
student1.speak(); // I can speak  Chinese

student2.speak(); // I can speak  Chinese

3)利用高阶函数的属性,给装饰器传参,通过参数来判断对类进行什么处理。

const withLanguage = (language) => (targetClass) => {
    targetClass.prototype.language = language;
}
@withLanguage('Chinese')
class Student {
}
const student = new Student();
student.language; // 'Chinese'

4)react-redux中,需要将store数据映射到组件中,connect 是一个高阶组件,它接收了两个函数

mapStateToProps 和 mapDispatchToProps 以及一个组件 App,最终返回了一个增强版的组件。
class App extends React.Component {
}
connect(mapStateToProps, mapDispatchToProps)(App)
使用装饰器写法:
@connect(mapStateToProps, mapDispatchToProps)
class App extends React.Component {
}

2.4 类属性装饰器

类属性装饰器可以用在类的属性、方法、get/set 函数中,一般会接收三个参数:

  1. target:被修饰的类
  2. name:类成员的名字
  3. descriptor:属性描述符,对象会将这个参数传给 Object.defineProperty
    使用类属性装饰器可以做到很多有意思的事情,比如 readonly 的例子:
function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}
class Person {
    @readonly name = 'person'
}
const person = new Person();
person.name = 'tom'; 
// 还可以用来统计一个函数的执行时间,以便于后期做一些性能优化。
function time(target, name, descriptor) {
    const func = descriptor.value;
    if (typeof func === 'function') {
        descriptor.value = function(...args) {
            console.time();
            const results = func.apply(this, args);
            console.timeEnd();
            return results;
        }
    }
}
class Person {
    @time
    say() {
        console.log('hello')
    }
}
const person = new Person();
person.say();

2.5 装饰器组合

如果想要多个装饰器,可以叠加使用

class Person {
    @time
    @log
    say() {}
}

在这里插入图片描述

3、应用

1)基本使用:

给一个类添加log或console,但如果这个方法所有类都要加,那得一个一个写,很麻烦。这时候可以用装饰器去拓展每一个class:

function addConcole(target) {
  // 拓展原型方法
  target.prototype.log = function(msg) {
    console.log(`[${new Date()} ${msg}`);
  };
  // 拓展静态属性
  target.myName = '一个类'
  return target;
}
@addConcole
class MyClass {
  constructor() {}
}

const myObj = new MyClass();
myObj.log('林三心');
// [Sat Jul 08 2023 17:31:55 GMT+0800 (中国标准时间) 林三心
console.log(MyClass.myName)
// 一个类

拓展原型方法,拓展静态属性

2)Node路由请求Url(类成员装饰器)

在使用一些 Node 的框架时,在写接口的时候,我们可能会经常看到这样的代码

  • 当我们请求路径是 GET doc 时会匹配到findDocById
  • 当我们请求路径是 POST doc 时会匹配到createDoc
class Doc {
  @Get('doc')
  async findDocById(id) {}
  
  @Post('doc')
  async createDoc(data) {}
}

其实这个 @Get 和 @Post ,是框架提供给我们的 类成员装饰器,是的,类成员也能使用装饰器,类成员装饰器接收三个参数:

  • target 是目标类的原型对象
  • key 表示目标类成员的键名
  • descriptor 是一个属性描述符对象,它包含目标类成员的属性特性(例如 value、writable 等)
function Get(path) {
  return function(target, key, descriptor) {
    console.log({
      target,
      key,
      descriptor
    })
  }
}

在这里插入图片描述

3)接口权限控制(类成员装饰器叠加)

  • GET doc 接口只能 管理员 才能访问
  • POST doc 接口只能 超级管理员 才能访问
function authenticated(target, key, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    if (isAuthenticated()) {
      originalMethod.apply(this, args);
    } else {
      console.log('Unauthorized access!');
    }
  };
  return descriptor;
}
class Doc {
  @Get('doc')
  @authenticated('admin')
  async findDocById(id) {}
  
  @Post('doc')
  @authenticated('superAdmin')
  async createDoc(data) {}
}

多个装饰器叠加执行顺序:从下往上, 离class定义最近的先执行。

4)记录日志的装饰器:

  • 函数调用时间
  • 函数调用参数
// 日志装饰器函数
function logDecorator(target, key, descriptor) {
  const originalMethod = descriptor.value; // 保存原始方法

  descriptor.value = function(...args) {
    console.log(`调用函数:${key}`);
    console.log(`参数:${JSON.stringify(args)}`);
    // 执行原始方法
    const result = originalMethod.apply(this, args);
    console.log(`返回值:${result}`);
    return result;
  };

  return descriptor;
}

// 示例类
class Example {
  @logDecorator
  greet(name) {
    return `Hello, ${name}!`;
  }
}

// 测试
const example = new Example();
example.greet('林三心');

5)缓存装饰器

// 缓存装饰器函数
function cacheDecorator(target, key, descriptor) {
  const cache = {}; // 缓存对象
  const originalMethod = descriptor.value; // 保存原始方法
  descriptor.value = function(...args) {
    const cacheKey = JSON.stringify(args); // 生成缓存键

    if (cacheKey in cache) {
      console.log('从缓存中获取结果');
      return cache[cacheKey]; // 直接返回缓存结果
    }

    // 执行原始方法
    const result = originalMethod.apply(this, args);
    console.log('将结果缓存起来');
    cache[cacheKey] = result; // 缓存结果
    return result;
  };
  return descriptor;
}

// 示例类
class Example {
  @cacheDecorator
  getValue(key) {
    console.log('执行函数逻辑');
    return key + Math.random(); // 模拟复杂的计算逻辑
  }
}

// 测试
const example = new Example();
console.log(example.getValue('foo'));
console.log(example.getValue('foo')); // 从缓存中获取结果

6)防抖节流

@debounce(500)
submit() {}

@throttle(200)
handleScroll() {}

7)错误处理装饰器

跟日志装饰器一样,错误其实也是日志的一部分,错误日志非常重要,因为 Nodejs 的线上报错,大部分都需要通过查日志来进行定位,所以我们也可以封装一个错误的处理装饰器~

function errorHandler(target, key, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args) {
    try {
      originalMethod.apply(this, args);
    } catch (error) {
      console.error(`Error occurred in ${key}:`, error);
    }
  };
  return descriptor;
}
// 使用:
class Common {
    @log()
    commonRequest(url, params) {
        return request(url, params)
    }
}

const common = new Common()
common.commonRequest('http://xxx.com', { name: 'l' })
Error occurred in commonRequest: Request Error

8)计时装饰器

function timing(target, key, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    console.log(`Execution time of ${key}: ${end - start} milliseconds`);
    return result;
  };
  return descriptor;
}

class Common {
    @timing()
    commonRequest(url, params) {
        return request(url, params)
    }
}
const common = new Common()
common.commonRequest()
Execution time of commonRequest: 20 milliseconds

9)参数检验装饰器

在没有ts类型监测的时候,可以用这个:

function validateArgs(...types) {
  return function (target, key, descriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args) {
      for (let i = 0; i < types.length; i++) {
        const type = types[i];
        const arg = args[i];
        if (typeof arg !== type) {
          throw new Error(`Invalid argument type at index ${i}`);
        }
      }
      originalMethod.apply(this, args);
    };
    return descriptor;
  };
}
class Common {
    @validateArgs(['string', 'object'])
    commonRequest(url, params) {
        return request(url, params)
    }
}
const common = new Common()
common.commonRequest(123, 123) // 报错

4、装饰器只能用于类上吗?为什么不能用于函数?—因为存在函数提升,类不会

var counter = 0;
 
var add = function () {
  counter++;
};
 
@add
function foo() {}

👆 想在执行函数的时候couter+1,但其实counter等于0,因为函数提升后是这样的:

@add
function foo() {
}
var counter;
var add;
counter = 0;
add = function () {
    counter++;
};

5、第三方 core-decorators.js,提供一下装饰器

@autobind:使得方法中的this对象,绑定原始对象
@readonly:使得属性或方法不可写。
@override:检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。
@deprecate (别名@deprecated):在控制台显示一条警告,表示该方法将废除。

6、目前已有项目上添加装饰器:

确保项目使用了 Babel 或 TypeScript 这样的工具来编译js 代码,因为装饰器是 ES7 的一个提案,需要转译为 ES5 以在现有浏览器中运行。

1)安装依赖:
对于babel需要添加一下依赖:

npm install --save-dev @babel/plugin-proposal-decorators 
# 或 
yarn add --dev @babel/plugin-proposal-decorators

对于 TypeScript,无需额外安装插件,装饰器已经内置在 TypeScript 中。
2)配置

对于 Babel,在 .babelrc 文件中添加以下配置:
{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }]
  ]
}

对于 TypeScript,确保 tsconfig.json 文件中有以下配置:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

参考+其他学习资料:
1、 ES7-装饰器Decorator详解
2、提案地址 JavaScript Decorators
3、 5分钟带你了解【前端装饰器】,“高大上”的“基础知识
4、 分享能提高开发效率,提高代码质量的八个前端装饰器函数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值