在前面的学习中,我们已经分别掌握了类、方法、属性、参数这 4 种装饰器是如何使用的。但在真实的架构设计(如 NestJS 或 Angular 源码)中,一个类往往会被密密麻麻的装饰器所包裹。
这时候,一个严肃的问题就摆在了我们面前:谁先执行,谁后执行? 如果顺序搞反了,权限校验可能就会在日志记录之前发生,从而导致系统逻辑混乱。今天,我们就来彻底搞清楚装饰器的执行 “生命周期”。
TypeScript 不同类型装饰器的优先级
在 TypeScript 中,如果一个类中同时存在多种类型的装饰器,它们的执行并不是随意的,而是遵循一套从 “微观” 到 “宏观” 的严密顺序:
- 实例成员:按在类中声明的先后顺序(从上到下)依次评估与执行。对于同一个方法而言,参数装饰器优先于方法装饰器执行。
- 静态成员:同样按在类中声明的 “先后顺序” 依次评估与执行。对于同一个方法而言,也是参数优先于方法。
- 构造函数:参数装饰器。
- 类装饰器:永远最后执行。
简单来说,TypeScript 的策略是:先装修房间内部(按代码从上到下扫荡实例和静态成员),最后再装修整栋大楼(类)。

TypeScript 同类型装饰器的 “洋葱模型”
在 TypeScript 中,当同一个目标(比如同一个方法)上贴了多个装饰器时,情况会变得更加有趣。TypeScript 采用了函数式编程中经典的 “洋葱模型(Onion Model)”,其核心逻辑是:“由外向内评估,由内向外执行”。
我们可以把装饰器工厂的求值过程想象成 “剥洋葱”,而把装饰器函数的实际执行想象成 “洋葱心的爆发”。
其执行规则如下:
- 评估(Evaluation):装饰器工厂函数从 “上到下” 依次执行。
- 调用(Call):装饰器真实的逻辑函数从 “下到上” 依次执行。

TypeScript 装饰器全家桶的生命周期
为了验证上述所有理论,我们编写一个包含所有装饰器类型的 “全能类”,并观察控制台的输出。
示例:全维度执行顺序测试(含静态成员)
// 1. 类装饰器工厂
function ClassDec(name: string) {
console.log("评估类装饰器:", name);
return (target: Function) => console.log("执行类装饰器:", name);
}
// 2. 属性装饰器工厂
function PropDec(name: string) {
console.log("评估属性装饰器:", name);
return (target: any, key: string) => console.log("执行属性装饰器:", name);
}
// 3. 方法装饰器工厂
function MethodDec(name: string) {
console.log("评估方法装饰器:", name);
return (target: any, key: string, desc: PropertyDescriptor) => console.log("执行方法装饰器:", name);
}
// 4. 参数装饰器工厂
function ParamDec(name: string) {
console.log("评估参数装饰器:", name);
return (target: any, key: string, index: number) => console.log("执行参数装饰器:", name);
}
@ClassDec("TopClass")
class TestExecutor {
// 实例成员(最先执行)
@PropDec("InstanceUsername")
public name: string = "Jack";
@MethodDec("OuterMethod")
@MethodDec("InnerMethod")
public saveData(@ParamDec("ParamFirst") id: number) {
console.log("业务代码执行中...");
}
// 静态成员(在实例成员之后执行)
@PropDec("StaticVersion")
public static version: string = "1.0";
}运行结果如下。
评估属性装饰器:InstanceUsername
执行属性装饰器:InstanceUsername
评估方法装饰器:OuterMethod
评估方法装饰器:InnerMethod
评估参数装饰器:ParamFirst
执行参数装饰器:ParamFirst
执行方法装饰器:InnerMethod
执行方法装饰器:OuterMethod
评估属性装饰器:StaticVersion
执行属性装饰器:StaticVersion
评估类装饰器:TopClass
执行类装饰器:TopClass分析:
想要真正地掌握 TypeScript,小伙伴们应该反复阅读上面的运行结果,因为这是理解 TypeScript 架构的灵魂所在:
- 实例优先于静态:我们可以清晰地看到,StaticVersion(静态属性)的评估与执行,严格排在了所有实例成员的后面.
- 声明顺序决定同级优先级:虽然 InstanceUsername(属性)和 OuterMethod(方法)同属实例成员,但因为属性代码写在最上面,所以它最先执行。这就打破了网上很多所谓 “属性一定先于方法” 的谣言。
- 参数优先于方法:虽然 @ParamDec 贴在 saveData() 方法内部,但它的执行优先级高于包裹它的 @MethodDec。
- 洋葱模型的嵌套:对于 saveData() 方法,TypeScript 先评估了外层的 OuterMethod,再评估了内层的 InnerMethod。但真正执行时,却是先执行 Inner,再执行 Outer。
- 类装饰器永远压轴:无论类装饰器写在代码的哪个位置,它永远是最后被触发的 “大楼封顶” 操作。
在实际的企业级项目(如 NestJS)中,掌握装饰器的执行顺序,能够帮我们避开 90% 的逻辑陷阱:
- 依赖注入(DI):参数装饰器先执行,是为了在方法运行前,确保所有需要的对象都已经 “标记” 好位置。
- 权限校验(运行时的穿透陷阱):这也是大厂面试中最爱挖坑的地方。虽然在类加载时,装饰器函数的求值与执行是 “由下而上” 依次进行的;但在程序真正运行时,由于外层的装饰器包裹了内层的装饰器,真正的业务拦截顺序其实是 “由上而下(由外向内)” 穿透的。
