阅读能解决问题-:
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 函数中,一般会接收三个参数:
- target:被修饰的类
- name:类成员的名字
- 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、 分享能提高开发效率,提高代码质量的八个前端装饰器函数