阅读能解决问题-:
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 Chinesestudent2.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 {@timesay() {console.log('hello')}
}
const person = new Person();
person.say();
2.5 装饰器组合
如果想要多个装饰器,可以叠加使用
class Person {@time@logsay() {}
}
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 {@logDecoratorgreet(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 {@cacheDecoratorgetValue(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详解 https://zoeice.com/es-decorator/
2、提案地址:JavaScript Decorators。
3、 5分钟带你了解【前端装饰器】,“高大上”的“基础知识” (qq.com)
4、 分享能提高开发效率,提高代码质量的八个前端装饰器函数~ (qq.com)