JavaScript 装饰器与元编程:构建声明式应用架构

2025年,JavaScript装饰器(Decorators)已经进入Stage 3,在TypeScript和Babel中广泛使用。元编程(Metaprogramming)通过ProxyReflect让开发者可以拦截和定制对象的基本操作。本文通过四个实战案例,带你掌握这些现代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支持。

JavaScript 装饰器与元编程:构建声明式应用架构
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 javascript JavaScript 装饰器与元编程:构建声明式应用架构 https://www.taomawang.com/web/javascript/1775.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务