Are you an LLM? You can read better optimized documentation at /fe/typescript/decorator-metadata-guide.md for this page in Markdown format
TypeScript 装饰器与元数据编程实战 2026 | Decorator 完全指南

装饰器(Decorator)是 TypeScript 中最强大也最令人困惑的特性之一。它让我们能在不修改原始代码的情况下,动态地给类、方法、属性添加功能。本文将系统讲解装饰器的原理、5 类装饰器用法、元数据编程、依赖注入及 AOP 实战。
一、装饰器基础
1.1 什么是装饰器
装饰器本质上是一个高阶函数,接收目标对象,返回增强后的对象:
typescript
// 装饰器的基本模式
function decorator(target) {
// 修改或增强 target
return target
}
// 使用装饰器
@decorator
class MyClass {}1.2 启用装饰器
json
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 启用元数据(需 reflect-metadata)
}
}安装元数据支持:
bash
npm install reflect-metadata1.3 装饰器执行顺序
装饰器的执行遵循 由内到外、由下到上 的原则:
typescript
function f1() {
console.log('f1: 求值')
return function (target: any) {
console.log('f1: 执行')
}
}
function f2() {
console.log('f2: 求值')
return function (target: any) {
console.log('f2: 执行')
}
}
@f1()
@f2()
class MyClass {}
// 输出:
// f1: 求值(先求值,从上到下)
// f2: 求值
// f2: 执行(后执行,从下到上)
// f1: 执行二、5 类装饰器详解
2.1 类装饰器(Class Decorator)
类装饰器接收类的构造函数作为参数:
typescript
function LogClass(target: Function) {
console.log(`类 ${target.name} 被装饰了`)
// 保存原始构造函数
const original = target
// 返回新的构造函数
return class extends original {
constructor(...args: any[]) {
super(...args)
console.log(`实例化 ${original.name},参数:${args}`)
}
}
}
@LogClass
class User {
constructor(public name: string) {}
}
const user = new User('Alice')
// 类 User 被装饰了
// 实例化 User,参数:Alice实用示例:单例模式装饰器
typescript
function Singleton<T extends new (...args: any[]) => any>(constructor: T) {
let instance: InstanceType<T>
return class extends constructor {
constructor(...args: any[]) {
if (instance) return instance
super(...args)
instance = this as InstanceType<T>
}
}
}
@Singleton
class Database {
constructor(public url: string) {
console.log('连接数据库...')
}
}
const db1 = new Database('mysql://localhost')
const db2 = new Database('mysql://remote')
// 连接数据库...(只输出一次)
console.log(db1 === db2) // true2.2 方法装饰器(Method Decorator)
方法装饰器接收 3 个参数:
target:对于静态方法是类的构造函数,对于实例方法是原型对象propertyKey:方法名descriptor:属性描述符(PropertyDescriptor)
typescript
function LogMethod(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value
descriptor.value = function (...args: any[]) {
console.log(`调用 ${propertyKey},参数:${JSON.stringify(args)}`)
const result = originalMethod.apply(this, args)
console.log(`返回值:${JSON.stringify(result)}`)
return result
}
return descriptor
}
class Calculator {
@LogMethod
add(a: number, b: number): number {
return a + b
}
}
const calc = new Calculator()
calc.add(2, 3)
// 调用 add,参数:[2,3]
// 返回值:5实用示例:防抖装饰器
typescript
function Debounce(delay: number) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value
let timer: NodeJS.Timeout
descriptor.value = function (...args: any[]) {
clearTimeout(timer)
timer = setTimeout(() => originalMethod.apply(this, args), delay)
}
return descriptor
}
}
class SearchComponent {
@Debounce(300)
onSearch(keyword: string) {
console.log(`搜索:${keyword}`)
}
}2.3 属性装饰器(Property Decorator)
属性装饰器接收 2 个参数:
target:原型对象或构造函数propertyKey:属性名
typescript
function Required(target: any, propertyKey: string) {
// 在原型上存储元数据
const requiredProps = Reflect.getMetadata('required', target) || []
requiredProps.push(propertyKey)
Reflect.defineMetadata('required', requiredProps, target)
}
function validate(instance: any) {
const target = Object.getPrototypeOf(instance)
const requiredProps: string[] = Reflect.getMetadata('required', target) || []
for (const prop of requiredProps) {
if (instance[prop] === undefined || instance[prop] === null) {
throw new Error(`属性 ${prop} 是必填的`)
}
}
}
class UserForm {
@Required
name!: string
@Required
email!: string
age?: number
}
const form = new UserForm()
form.name = 'Alice'
// validate(form) // Error: 属性 email 是必填的2.4 访问器装饰器(Accessor Decorator)
访问器装饰器与方法装饰器参数相同,但只能装饰 getter 或 setter 中的一个:
typescript
function Enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value
return descriptor
}
}
class Person {
private _name: string = ''
@Enumerable(false)
get name(): string {
return this._name
}
set name(value: string) {
this._name = value
}
}2.5 参数装饰器(Parameter Decorator)
参数装饰器接收 3 个参数:
target:原型对象或构造函数propertyKey:方法名(构造函数参数为undefined)parameterIndex:参数索引
typescript
function Inject(token: string) {
return function (target: any, propertyKey: string | undefined, parameterIndex: number) {
// 将依赖注入信息存入元数据
const existingTokens = Reflect.getMetadata('inject:tokens', target, propertyKey || 'constructor') || []
existingTokens[parameterIndex] = token
Reflect.defineMetadata('inject:tokens', existingTokens, target, propertyKey || 'constructor')
}
}
class UserService {
constructor(@Inject('Database') private db: any) {}
getUser(@Inject('Logger') logger: any, id: number) {
logger.log(`获取用户 ${id}`)
}
}三、reflect-metadata 与元数据编程
3.1 核心概念
reflect-metadata 允许在对象上添加和读取元数据,这是依赖注入和 AOP 的基石:
typescript
import 'reflect-metadata'
// 定义元数据
Reflect.defineMetadata('role', 'admin', MyClass)
Reflect.defineMetadata('version', '1.0', MyClass.prototype, 'method')
// 读取元数据
const role = Reflect.getMetadata('role', MyClass) // 'admin'
const version = Reflect.getMetadata('version', MyClass.prototype, 'method') // '1.0'
// 检查元数据是否存在
Reflect.hasMetadata('role', MyClass) // true
// 删除元数据
Reflect.deleteMetadata('role', MyClass)3.2 内置元数据键
typescript
// 类型元数据(需 emitDecoratorMetadata: true)
class Example {
greet(@Reflect.metadata('design:type', String) name: string) {}
method(
@Reflect.metadata('design:paramtypes', [String, Number])
input: string
) {}
}
// 自动记录的类型信息
// design:type —— 属性的类型
// design:paramtypes —— 方法参数的类型数组
// design:returntype —— 方法的返回类型3.3 装饰器工厂 + 元数据
typescript
function Route(method: string, path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 将路由信息存入元数据
Reflect.defineMetadata('route:method', method, target, propertyKey)
Reflect.defineMetadata('route:path', path, target, propertyKey)
}
}
function Middleware(middleware: Function) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const middlewares = Reflect.getMetadata('route:middlewares', target, propertyKey) || []
middlewares.push(middleware)
Reflect.defineMetadata('route:middlewares', middlewares, target, propertyKey)
}
}
class UserController {
@Route('GET', '/users/:id')
@Middleware(authMiddleware)
getUser() {}
@Route('POST', '/users')
@Middleware(authMiddleware)
@Middleware(validateUser)
createUser() {}
}四、实战:手写轻量级依赖注入容器
4.1 容器实现
typescript
import 'reflect-metadata'
type Constructor<T = any> = new (...args: any[]) => T
// 依赖注入容器
class DIContainer {
private bindings = new Map<string, any>()
private instances = new Map<string, any>()
// 注册绑定
bind<T>(token: string, constructor: Constructor<T>) {
this.bindings.set(token, constructor)
return this
}
// 注册单例
singleton<T>(token: string, constructor: Constructor<T>) {
this.bindings.set(token, { constructor, singleton: true })
return this
}
// 注册常量
value<T>(token: string, value: T) {
this.instances.set(token, value)
return this
}
// 解析依赖
resolve<T>(token: string): T {
// 先检查已实例化的单例
if (this.instances.has(token)) {
return this.instances.get(token)
}
const binding = this.bindings.get(token)
if (!binding) {
throw new Error(`未找到绑定:${token}`)
}
const Constructor = binding.singleton ? binding.constructor : binding
const isSingleton = binding.singleton
// 读取构造函数参数的类型信息
const paramTypes: Constructor[] = Reflect.getMetadata('design:paramtypes', Constructor) || []
// 递归解析依赖
const dependencies = paramTypes.map((paramType, index) => {
const injectToken = Reflect.getMetadata('inject:token', Constructor, `constructor:${index}`)
return this.resolve(injectToken || paramType.name)
})
const instance = new Constructor(...dependencies)
if (isSingleton) {
this.instances.set(token, instance)
}
return instance
}
}
// Injectable 装饰器
function Injectable() {
return function (target: any) {
// 标记该类可被注入
Reflect.defineMetadata('injectable', true, target)
}
}
// Inject 装饰器
function Inject(token: string) {
return function (target: any, propertyKey: string | undefined, parameterIndex: number) {
Reflect.defineMetadata(`inject:token`, token, target, `constructor:${parameterIndex}`)
}
}4.2 使用容器
typescript
@Injectable()
class Logger {
log(msg: string) {
console.log(`[LOG] ${new Date().toISOString()} - ${msg}`)
}
}
@Injectable()
class Database {
constructor(private logger: Logger) {}
query(sql: string) {
this.logger.log(`执行查询:${sql}`)
return [{ id: 1, name: 'Alice' }]
}
}
@Injectable()
class UserService {
constructor(
@Inject('Database') private db: Database,
@Inject('Logger') private logger: Logger
) {}
getUser(id: number) {
this.logger.log(`获取用户 ${id}`)
return this.db.query(`SELECT * FROM users WHERE id = ${id}`)
}
}
// 配置容器
const container = new DIContainer()
container.bind('Logger', Logger)
container.singleton('Database', Database)
container.bind('UserService', UserService)
// 解析使用
const userService = container.resolve<UserService>('UserService')
userService.getUser(1)五、AOP(面向切面编程)实战
5.1 AOP 核心概念
- 切面(Aspect):横切关注点的模块化(如日志、权限、缓存)
- 切点(Pointcut):定义在哪些方法上应用切面
- 通知(Advice):切面的具体逻辑(前置、后置、环绕)
- 织入(Weaving):将切面应用到目标对象的过程
5.2 用装饰器实现 AOP
typescript
// 前置通知
function Before(advice: (...args: any[]) => void) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
advice(...args)
return original.apply(this, args)
}
}
}
// 后置通知
function After(advice: (result: any) => void) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
const result = original.apply(this, args)
advice(result)
return result
}
}
}
// 环绕通知
function Around(advice: (proceed: () => any, ...args: any[]) => any) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
const proceed = () => original.apply(this, args)
return advice(proceed, ...args)
}
}
}
// 异常通知
function Catch(errorHandler: (error: Error) => void) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
try {
return original.apply(this, args)
} catch (error) {
errorHandler(error as Error)
return null
}
}
}
}5.3 实战:缓存切面
typescript
function Cache(ttl: number = 60000) {
const cache = new Map<string, { value: any; expire: number }>()
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
const key = `${propertyKey}:${JSON.stringify(args)}`
const cached = cache.get(key)
if (cached && Date.now() < cached.expire) {
console.log(`缓存命中:${key}`)
return cached.value
}
const result = original.apply(this, args)
cache.set(key, { value: result, expire: Date.now() + ttl })
console.log(`缓存写入:${key}`)
return result
}
}
}
class APIService {
@Cache(30000)
async getUser(id: number) {
console.log(`请求 API:GET /users/${id}`)
return { id, name: 'Alice' }
}
}六、TC39 Stage 3 装饰器新语法
2026 年,TC39 装饰器提案已进入 Stage 3,语法与旧版有所不同:
6.1 新语法差异
typescript
// 旧语法(TypeScript 实验性装饰器)
function logged(target: any, key: string, descriptor: PropertyDescriptor) {
// ...
}
// 新语法(TC39 Stage 3)
function logged(value: Function, context: ClassMethodDecoratorContext) {
return function (...args: any[]) {
console.log(`调用 ${String(context.name)}`)
return value.apply(this, args)
}
}
class MyClass {
@logged
greet(name: string) {
return `Hello, ${name}!`
}
}6.2 Context 对象
typescript
// 类装饰器上下文
interface ClassDecoratorContext {
kind: 'class'
name: string
addInitializer(initializer: () => void): void
}
// 方法装饰器上下文
interface ClassMethodDecoratorContext {
kind: 'method'
name: string
static: boolean
private: boolean
access: { has(object: any): boolean; get(object: any): unknown }
addInitializer(initializer: () => void): void
}
// 属性装饰器上下文
interface ClassFieldDecoratorContext {
kind: 'field'
name: string
static: boolean
private: boolean
access: { has(object: any): boolean; get(object: any): unknown }
addInitializer(initializer: () => void): void
}6.3 新语法示例
typescript
// 使用新语法的装饰器
function bound(value: Function, context: ClassMethodDecoratorContext) {
const methodName = String(context.name)
if (context.kind !== 'method') {
throw new Error(`@bound 只能用于方法,不能用于 ${context.kind}`)
}
return function (this: any, ...args: any[]) {
return value.apply(this, args)
}
}
function tracked(_: undefined, context: ClassFieldDecoratorContext) {
const fieldName = String(context.name)
return function (this: any, initialValue: unknown) {
console.log(`${fieldName} 初始化为 ${initialValue}`)
return initialValue
}
}
class Button {
@tracked
clicks = 0
@bound
onClick() {
this.clicks++
console.log(`点击次数:${this.clicks}`)
}
}
const btn = new Button()
const handler = btn.onClick // this 不会丢失
handler() // 点击次数:1七、NestJS 中的装饰器应用
NestJS 是装饰器与依赖注入的集大成者:
typescript
import { Controller, Get, Post, Body, Param, UseGuards, SetMetadata } from '@nestjs/common'
// 自定义装饰器
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
@Controller('users')
export class UserController {
constructor(private userService: UserService) {}
@Get()
@UseGuards(AuthGuard)
@Roles('admin')
findAll() {
return this.userService.findAll()
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id)
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto)
}
}八、最佳实践与注意事项
- 保持装饰器纯粹:装饰器应该只添加元数据或修改行为,不要包含复杂业务逻辑
- 组合优于继承:用装饰器组合功能比继承更灵活
- 注意执行顺序:多个装饰器的执行顺序很重要,文档中要明确说明
- 类型安全:尽量给装饰器添加泛型约束,避免
any - 性能考量:装饰器在类定义时执行一次,运行时开销来自 descriptor 代理
- 兼容性:TC39 新旧语法不兼容,新项目优先使用 Stage 3 语法
- 调试困难:装饰器会隐藏调用栈,关键路径加日志
九、总结
- ✅ 掌握了 5 类装饰器的用法与参数(类、方法、属性、访问器、参数)
- ✅ 理解 reflect-metadata 元数据编程原理
- ✅ 实现了轻量级依赖注入容器
- ✅ 用装饰器实现了 AOP(日志、缓存、权限等切面)
- ✅ 了解 TC39 Stage 3 装饰器新语法
- ✅ 在 NestJS 中实战应用装饰器
装饰器是 TypeScript 高级编程的核心工具,熟练掌握后可以大幅提升代码的可维护性和可扩展性。
相关阅读: