TypeScript泛型<T>继承构造函数类型的原因及装饰器逻辑解析
1. 泛型<T>为什么要继承{ new (...args: any[]): {} }?
这个类型是构造函数签名,用来约束泛型T必须是一个可实例化的类。因为我们的装饰器是用来装饰类的,传入的classConstructor必须是能通过new关键字创建实例的构造函数。
具体拆解:
new (...args: any[]):表示这个构造函数可以接收任意数量、任意类型的参数: {}:表示构造函数执行后返回一个对象(也就是类的实例)
用这个约束能让TypeScript编译器自动校验传入的对象是否是合法的类构造函数,避免把非构造函数类型误传给装饰器导致错误。
2. 被继承的对象如何接收参数?
被继承的原类(这里是Person)通过子类构造器里的super(...args)接收参数。
比如当我们执行const p = new Person("Alice", 30)时,传入的"Alice"和30会被子类构造器的...args捕获,然后通过super(...args)传递给父类Person的构造器,父类就能正常初始化自身的name和age属性。
3. 装饰器函数的完整执行逻辑
我们一步步拆解整个流程:
步骤1:执行装饰器工厂函数
@AddLocation(1.234, 1.876)首先会触发AddLocation(1.234, 1.876)的执行,这个函数接收lat和long参数后,返回一个真正的装饰器函数(即接收classConstructor的箭头函数)。这一步会把lat和long保存在闭包里,后续可以直接使用。
步骤2:装饰器函数接收原类
返回的装饰器函数会被传入被装饰的Person类作为参数classConstructor。此时泛型约束会确保Person是符合构造函数签名的合法类型。
步骤3:创建并返回子类
装饰器函数返回一个继承自Person的子类:
- 子类新增了
mapLocation属性,类型为MapLocation - 子类构造器接收任意参数
...args,首先调用super(...args)把参数传给父类Person的构造器,完成父类属性的初始化 - 最后给当前实例的
mapLocation赋值为闭包中保存的lat和long
步骤4:替换原类
TypeScript会用这个子类替换原来的Person类。之后我们创建Person实例时,实际创建的是这个子类的实例——它既有原Person的name、age属性,又有新增的mapLocation属性。
举个实例化的验证例子:
const person = new Person("Bob", 25); console.log(person.name); // "Bob" console.log(person.age); // 25 console.log(person.mapLocation); // { lat: 1.234, long: 1.876 }
内容的提问来源于stack exchange,提问作者Karan Badhwar

