TypeScript 装饰器与元编程:从类装饰到参数装饰

背景与问题界定 TypeScript 装饰器经历了从实验性特性到 ECMAScript 标准的演进,目前 5.x 版本同时支持传统的实验性装饰器和 ESMirror 规范的新式装饰器。然而,许多团队对装饰器的认知仍然停留在"给类加个标记"的层面,没有充分发挥其在 AOP 侧切面编程中的能力。实际工程中,基础设施代码与业务逻辑的横切关注点混杂——每个 Controller 方法开头重复的权限检查、参数校验、日志记录形成了大量模板代码。Decorator 正好是解决这类横切问题的天然工具。但同时也面临着类型安全、装饰器执行顺序、以及新旧装饰器语法兼容性等工程挑战。 目标拆解与工程约束 装饰器类型安全:自定义装饰器应使用泛型保留被装饰目标的原始类型签名,不破坏 TypeScript 的静态类型推导。 执行顺序可预测:多个装饰器叠加时,执行顺序遵循"自下而上应用、自外向内执行"的契约,团队应有统一规范记录装饰器组合的语义。 新旧装饰器兼容:项目在迁移到 Stage 3 装饰器过程中,提供过渡工具确保两种装饰器语法能在不同模块中共存。 运行时开销可控:装饰器的元数据解析和代理创建不应显著影响热路径性能,需要提供 Benchmark 基准。 方案设计 我们将装饰器的应用场景分为四类,分别对应四种装饰器类型:类装饰器用于依赖注入容器注册和单例管理;方法装饰器用于日志切片、权限校验和重试控制;访问器装饰器用于计算属性的缓存和延迟加载;参数装饰器用于参数验证和依赖注入的参数标记。 以方法装饰器为例,一个通用的日志装饰器实现可以在不侵入业务逻辑的前提下,自动采集方法调用的入参、返回值和耗时: function Log(level: 'info' | 'warn' | 'error' = 'info') { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value descriptor.value = function (...args: any[]) { const start = performance.now() try { const result = original.apply(this, args) if (result instanceof Promise) { return result.then(r => { logger.log(level, `${propertyKey} succeeded`, { args, duration: performance.now() - start }) return r }).catch(err => { logger.error(`${propertyKey} failed`, { args, error: err }) throw err }) } logger.log(level, `${propertyKey} succeeded`, { args, duration: performance.now() - start }) return result } catch (err) { logger.error(`${propertyKey} failed`, { args, error: err }) throw err } } return descriptor } } 对于依赖注入场景,我们使用类装饰器 + reflect-metadata 实现一个轻量级的 DI 容器。通过在构造函数的参数上使用 @Inject 参数装饰器标记依赖,容器在实例化时自动解析依赖树并注入。这种方法在 NestJS 中已被大规模验证,但在纯前端项目中同样适用——只需要一个简洁的容器实现和几个装饰器辅助函数。 ...

2026年8月6日 · 1 分钟 · BvBeJ

C++ 反射实现:从 0 到依赖注入容器

背景 C++ 一直缺少反射(Reflection),虽然 C++20 引入了 std::reflect,但目前编译器支持还很有限。 实际项目中,我们经常需要: 根据字符串创建对象 自动注入依赖 序列化/反序列化 这篇文章聊聊怎么在 C++ 里实现一套轻量反射系统。 反射的核心:类型注册 反射的本质是在运行时动态查询类型信息。实现思路很简单——用全局注册表。 1. 基础类型注册表 #include <functional> #include <unordered_map> #include <string> #include <memory> class TypeRegistry { public: template<typename T> static void registerType(const std::string& name) { creators()[name] = []() -> std::any { return std::make_any<T>(); }; } static std::any create(const std::string& name) { auto it = creators().find(name); if (it != creators().end()) { return it->second(); } throw std::runtime_error("Unknown type: " + name); } private: static auto& creators() { static std::unordered_map<std::string, std::function<std::any()>> map; return map; } }; 2. 宏简化注册 #define REGISTER_TYPE(T) \ namespace { \ struct Registrar##T { \ Registrar##T() { \ TypeRegistry::registerType<T>(#T); \ } \ }; \ static Registrar##T registrar_##T; \ } 3. 使用 struct User { std::string name; int age; }; REGISTER_TYPE(User) int main() { auto any_user = TypeRegistry::create("User"); auto& user = std::any_cast<User&>(any_user); user.name = "BvBeJ"; user.age = 28; } 进阶:带构造函数参数 上面的实现只能调用默认构造函数。实际场景往往需要传参: ...

2026年4月6日 · 2 分钟 · BvBeJ