2025年,JavaScript装饰器(Decorators)已经进入Stage 3,在TypeScript和Babel中广泛使用。元编程(Metaprogramming)通过Proxy和Reflect让开发者可以拦截和定制对象的基本操作。本文通过四个实战案例,带你掌握这些现代JavaScript特性。
1. 为什么需要装饰器与元编程?
传统JavaScript在修改类行为时需要手动包装或继承,代码冗余且容易出错。装饰器提供声明式的方式增强类和方法,元编程则允许在运行时拦截和修改语言级别的操作。
- 装饰器:声明式修改类、方法、属性、参数
- Proxy:拦截对象操作(get、set、apply等)
- Reflect:执行默认对象操作的元编程API
2. 装饰器基础:定义与使用
JavaScript装饰器是特殊函数,用于修改类、方法、属性或参数的行为。
// 类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class UserService {
constructor(public name: string) {}
}
// 方法装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`调用 ${propertyKey},参数:`, args);
const result = originalMethod.apply(this, args);
console.log(`返回值:`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
// 属性装饰器
function readonly(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
writable: false
});
}
class Config {
@readonly
apiUrl = 'https://api.example.com';
}
// 使用
const calc = new Calculator();
calc.add(2, 3); // 输出日志
3. 实战案例一:装饰器实现依赖注入
使用装饰器构建轻量级依赖注入容器。
// 注入装饰器
function Injectable(target: any) {
Reflect.defineMetadata('injectable', true, target);
}
function Inject(token?: string) {
return function (target: any, propertyKey: string, parameterIndex: number) {
const existingInjections = Reflect.getOwnMetadata('injections', target) || [];
existingInjections.push({ index: parameterIndex, token });
Reflect.defineMetadata('injections', existingInjections, target);
};
}
// 服务定义
@Injectable
class LoggerService {
log(message: string) {
console.log(`[LOG] ${message}`);
}
}
@Injectable
class EmailService {
send(to: string, subject: string) {
console.log(`发送邮件到 ${to}: ${subject}`);
}
}
@Injectable
class UserController {
constructor(
@Inject('LoggerService') private logger: LoggerService,
@Inject('EmailService') private email: EmailService
) {}
register(username: string) {
this.logger.log(`用户 ${username} 注册`);
this.email.send(`${username}@example.com`, '欢迎注册');
}
}
// 简易DI容器
class Container {
private instances = new Map();
private factories = new Map();
register(token: string, factory: () => any) {
this.factories.set(token, factory);
}
resolve(target: any): any {
if (this.instances.has(target.name)) {
return this.instances.get(target.name);
}
const injections = Reflect.getOwnMetadata('injections', target) || [];
const args = injections
.sort((a, b) => a.index - b.index)
.map(inj => {
const token = inj.token || inj.index.toString();
return this.resolveByName(token);
});
const instance = new target(...args);
this.instances.set(target.name, instance);
return instance;
}
private resolveByName(token: string): any {
if (this.instances.has(token)) {
return this.instances.get(token);
}
const factory = this.factories.get(token);
if (factory) {
const instance = factory();
this.instances.set(token, instance);
return instance;
}
throw new Error(`无法解析: ${token}`);
}
}
// 使用容器
const container = new Container();
container.register('LoggerService', () => new LoggerService());
container.register('EmailService', () => new EmailService());
const controller = container.resolve(UserController);
controller.register('张三');
4. 实战案例二:Proxy实现响应式系统
使用Proxy构建类似Vue的响应式数据系统。
// 依赖收集器
class Dep {
private subscribers = new Set();
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => effect());
}
}
let activeEffect = null;
function watchEffect(effect: Function) {
activeEffect = effect;
effect();
activeEffect = null;
}
// 响应式系统
function reactive(target: T): T {
const depsMap = new Map();
return new Proxy(target, {
get(obj, key: string) {
if (!depsMap.has(key)) {
depsMap.set(key, new Dep());
}
depsMap.get(key)!.depend();
return Reflect.get(obj, key);
},
set(obj, key: string, value) {
const result = Reflect.set(obj, key, value);
if (depsMap.has(key)) {
depsMap.get(key)!.notify();
}
return result;
}
});
}
// 计算属性
function computed(getter: () => T): { value: T } {
const ref = { value: getter() };
watchEffect(() => {
ref.value = getter();
});
return ref;
}
// 使用示例
const state = reactive({
count: 0,
name: '张三'
});
watchEffect(() => {
console.log(`计数变化: ${state.count}`);
});
const double = computed(() => state.count * 2);
console.log('初始值:', double.value); // 0
state.count = 5; // 触发日志和computed更新
console.log('更新后:', double.value); // 10
5. 实战案例三:Proxy实现数据验证
使用Proxy拦截对象属性设置,实现自动数据验证。
// 验证规则定义
interface ValidationRule {
type: 'string' | 'number' | 'email' | 'boolean';
required?: boolean;
min?: number;
max?: number;
pattern?: RegExp;
}
// 验证装饰器
function Validate(rules: Record) {
return function (constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(...args);
return createValidatedProxy(this, rules);
}
};
};
}
function createValidatedProxy(target: T, rules: Record): T {
return new Proxy(target, {
set(obj, key: string, value) {
const rule = rules[key];
if (rule) {
// 必填验证
if (rule.required && (value === undefined || value === null || value === '')) {
throw new Error(`${key} 是必填字段`);
}
// 类型验证
if (value !== undefined && value !== null) {
switch (rule.type) {
case 'string':
if (typeof value !== 'string') {
throw new Error(`${key} 必须是字符串`);
}
if (rule.min !== undefined && value.length rule.max) {
throw new Error(`${key} 最大长度 ${rule.max}`);
}
break;
case 'number':
if (typeof value !== 'number' || isNaN(value)) {
throw new Error(`${key} 必须是数字`);
}
if (rule.min !== undefined && value rule.max) {
throw new Error(`${key} 最大值 ${rule.max}`);
}
break;
case 'email':
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
if (!emailRegex.test(value)) {
throw new Error(`${key} 不是有效的邮箱`);
}
break;
}
if (rule.pattern && !rule.pattern.test(value)) {
throw new Error(`${key} 格式不正确`);
}
}
}
return Reflect.set(obj, key, value);
}
});
}
// 使用验证装饰器
@Validate({
username: { type: 'string', required: true, min: 2, max: 50 },
email: { type: 'email', required: true },
age: { type: 'number', required: true, min: 18, max: 120 },
isActive: { type: 'boolean' }
})
class UserForm {
username: string = '';
email: string = '';
age: number = 0;
isActive: boolean = false;
}
// 测试
const form = new UserForm();
try {
form.username = '张三';
form.email = 'zhangsan@example.com';
form.age = 28;
form.isActive = true;
console.log('验证通过:', form);
} catch (e) {
console.error('验证失败:', e.message);
}
// 测试错误
try {
form.email = 'invalid-email';
} catch (e) {
console.error('邮箱验证失败:', e.message);
}
6. 实战案例四:装饰器实现API限流
使用方法装饰器实现接口频率限制。
// 限流装饰器
function RateLimit(maxRequests: number, windowMs: number) {
const requestLog = new Map();
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const key = `${target.constructor.name}.${propertyKey}`;
const now = Date.now();
if (!requestLog.has(key)) {
requestLog.set(key, []);
}
const timestamps = requestLog.get(key)!;
// 清除过期记录
const windowStart = now - windowMs;
while (timestamps.length > 0 && timestamps[0] = maxRequests) {
const retryAfter = Math.ceil((timestamps[0] + windowMs - now) / 1000);
throw new Error(`请求过于频繁,请 ${retryAfter} 秒后重试`);
}
timestamps.push(now);
return originalMethod.apply(this, args);
};
return descriptor;
};
}
// API服务类
class ApiService {
@RateLimit(5, 10000) // 10秒内最多5次请求
async fetchUserData(userId: string) {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 100));
return { id: userId, name: '张三', email: 'zhangsan@example.com' };
}
@RateLimit(3, 60000) // 1分钟内最多3次请求
async updateUserProfile(userId: string, data: any) {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 200));
return { success: true };
}
}
// 测试限流
async function testRateLimit() {
const api = new ApiService();
// 快速连续请求
for (let i = 0; i setTimeout(resolve, 500));
}
}
testRateLimit();
7. 性能对比:传统方式 vs 装饰器+Proxy
| 场景 | 传统方式 | 装饰器+Proxy方式 |
|---|---|---|
| 依赖注入 | 手动创建实例,工厂模式 | 声明式注入,自动解析 |
| 响应式系统 | 需要手动触发更新 | Proxy自动拦截,自动通知 |
| 数据验证 | 每个setter中手动验证 | Proxy统一拦截,声明式规则 |
| API限流 | 每个方法内手动实现 | 装饰器复用,声明式配置 |
8. 最佳实践总结
- 装饰器适合横切关注点:日志、缓存、权限、限流
- Proxy适合对象级拦截:验证、响应式、数据绑定
- Reflect作为默认操作:在Proxy中使用Reflect确保正确行为
- 元数据管理:使用Reflect.defineMetadata存储装饰器信息
- 性能考虑:Proxy有轻微性能开销,不适合高频调用场景
// 最佳实践:组合装饰器和Proxy
function Observable(target: any) {
const instance = new target();
return createReactiveProxy(instance);
}
function createReactiveProxy(target: T): T {
return new Proxy(target, {
set(obj, key, value) {
const oldValue = Reflect.get(obj, key);
const result = Reflect.set(obj, key, value);
if (oldValue !== value) {
// 触发更新事件
dispatchEvent(new CustomEvent('statechange', {
detail: { key, oldValue, newValue: value }
}));
}
return result;
}
});
}
// 使用Reflect进行安全元操作
function getMetadata(target: any, key: string) {
return Reflect.getMetadata ? Reflect.getMetadata(key, target) : undefined;
}
9. 总结
通过本文的案例,你掌握了JavaScript装饰器和元编程的核心技术:
- 类装饰器、方法装饰器、属性装饰器
- 装饰器实现依赖注入容器
- Proxy实现响应式系统和数据验证
- 装饰器实现API限流
- 最佳实践与性能对比
JavaScript装饰器和元编程让代码更加声明式、可复用和可维护。这些现代JavaScript特性将大幅提升你的开发效率和代码质量。现在就开始在你的项目中实践这些特性吧!
本文原创,基于ES2022+和TypeScript 5.0+。装饰器语法需要TypeScript或Babel支持。

