09. JS 装饰器
1. 装饰器是什么?
装饰器(Decorator)是一种特殊类型的声明,用于为类、方法、属性或访问器添加元编程功能。其本质是一个函数,通过@
符号作为标识符,可以:
- 修改类定义
- 扩展方法行为
- 添加元数据
- 实现AOP(面向切面编程)
示例:
javascript
@log // 类装饰器
class Calculator {
@memoize // 方法装饰器
sum(a, b) {
return a + b
}
}
2. JavaScript 装饰器的起源
发展历程:
- 2014 年首次出现在 TypeScript 1.5
- 2018 年进入 TC39 提案 Stage 2
- 2022 年更新为 Stage 3(当前状态)
- 需要 Babel/TypeScript 转译支持
标准实现差异:
环境 | 语法支持 | 配置方式 |
---|---|---|
Babel | 需要插件 | @babel/plugin-proposal-decorators |
TypeScript | 实验特性 | tsconfig.json 中开启 experimentalDecorators |
3. 类的装饰
基本结构:
javascript
function classDecorator(target) {
return class extends target {
// 扩展类功能
}
}
@classDecorator
class MyClass {}
参数说明:
- 参数 1:被装饰的类构造函数
- 返回值:新的类构造函数
示例:添加版本信息
javascript
function withVersion(version) {
return function(target) {
target.prototype.version = version
return target
}
}
@withVersion('1.0.0')
class App {}
4. 方法或成员的装饰
方法装饰器签名:
javascript
function methodDecorator(target, name, descriptor) {
const originalMethod = descriptor.value
descriptor.value = function(...args) {
// 方法逻辑扩展
return originalMethod.apply(this, args)
}
return descriptor
}
参数说明:
参数 | 说明 |
---|---|
target | 类的原型对象 |
name | 被装饰成员的名称 |
descriptor | 属性描述符(Object.defineProperty 使用) |
属性描述符结构:
javascript
{
value: 当前值,
enumerable: 是否可枚举,
configurable: 是否可配置,
writable: 是否可写
}
示例:防抖装饰器
javascript
function debounce(delay = 300) {
return function(target, name, descriptor) {
let timeout
const original = descriptor.value
descriptor.value = function(...args) {
clearTimeout(timeout)
timeout = setTimeout(() => {
original.apply(this, args)
}, delay)
}
return descriptor
}
}
class SearchBox {
@debounce(500)
search(query) {
// 搜索逻辑
}
}
5. 为什么装饰器不用于函数
规范限制原因:
- 函数提升(Hoisting)导致装饰顺序不可控
- 箭头函数的
this
绑定问题 - 避免与函数表达式混淆
错误示例:
javascript
// 以下写法在规范中不被支持
@log
function myFunction() {}
替代方案:
javascript
// 使用高阶函数包装
const decoratedFn = log(function() {})
6. 实践:实现日志记录
完整日志装饰器实现:
javascript
function log(target, name, descriptor) {
const original = descriptor.value
descriptor.value = function(...args) {
console.log(`[调用] ${name} 参数: ${JSON.stringify(args)}`)
const start = Date.now()
const result = original.apply(this, args)
console.log(`[完成] ${name} 耗时: ${Date.now() - start}ms`)
return result
}
return descriptor
}
class API {
@log
async fetchData(url) {
const response = await fetch(url)
return response.json()
}
}
// 使用示例
const api = new API()
api.fetchData('https://api.example.com/data')
输出结果:
text
[调用] fetchData 参数: ["https://api.example.com/data"]
[完成] fetchData 耗时: 352ms
配置指南:
- 安装 Babel 支持bash
npm install --save-dev @babel/plugin-proposal-decorators
- 配置
.babelrc
json{ "plugins": [ ["@babel/plugin-proposal-decorators", { "version": "2022-03" }] ] }