TypeScript对象字面量类型调用签名与泛型等价性技术问询
让我一步步帮你拆解这些问题,先从基础概念说起:
首先,调用签名是TypeScript用来描述一个可以被调用的对象(比如函数)的调用行为的语法。在JavaScript里,函数本身就是一种特殊的对象——你可以调用它,同时它也能拥有属性。
对象字面量类型的调用签名,就是用对象字面量的形式来定义这种可调用行为。比如,普通的函数类型写法是:
type StringifyFunc = (num: number) => string;
而等价的对象字面量调用签名写法是:
type StringifyFunc = { (num: number): string };
这两种写法描述的是同一个类型:一个接受number参数、返回string的可调用对象。
泛型和对象字面量调用签名结合有两种常见方式:
1. 泛型作用于调用签名本身(调用时推断泛型)
把泛型参数放在调用签名的括号前,意味着每次调用这个函数时,TypeScript会自动推断泛型的类型:
type GenericIdentity = { <T>(arg: T): T; }; const identity: GenericIdentity = (arg) => arg; // 调用时自动推断T为number identity(123); // 推断T为string identity("hello");
2. 泛型作用于整个对象类型(定义时指定泛型)
把泛型参数放在对象类型的名称后面,意味着在使用这个类型时必须先指定泛型参数,之后函数的参数和返回值类型就固定了:
type GenericIdentity<T> = { (arg: T): T; }; // 指定T为number,这个函数只能接受number类型参数 const numberIdentity: GenericIdentity<number> = (arg) => arg; numberIdentity(123); // 正确 numberIdentity("hello"); // 报错
你提到的文档里的示例1和示例2,应该类似这样:
示例1:泛型函数类型
type IdentityFunc<T> = (arg: T) => T;示例2:对象字面量调用签名的泛型类型
type IdentityFunc<T> = { (arg: T): T };
TypeScript把(arg: T) => T这种函数类型语法,当成了{ (arg: T): T }的语法糖。本质上,这两种写法描述的是完全相同的类型——因为函数在JS中是对象,TypeScript用这两种方式来表达“这个对象可以被调用,且调用规则符合签名”。
你可以通过赋值兼容性来验证等价性:
type Identity1<T> = (arg: T) => T; type Identity2<T> = { (arg: T): T }; const id1: Identity1<number> = (x) => x; const id2: Identity2<number> = id1; // 完全兼容,没有报错 const id3: Identity1<string> = id2; // 反过来也成立
那为什么要有两种写法?因为对象字面量调用签名的优势是可以同时描述函数的调用行为和它的其他属性。比如,你可以定义一个既有调用能力又有属性的对象类型:
type Logger = { (message: string): void; logLevel: "info" | "warn" | "error"; }; const consoleLogger: Logger = (message) => console.log(message); consoleLogger.logLevel = "info";
这种场景下,普通的函数类型(message: string) => void就无法描述logLevel属性了,而对象字面量调用签名可以完美结合两者。
对象字面量调用签名还有一种变体叫构造签名,用new关键字开头,用来描述可以用new调用的构造函数,比如:
type PersonConstructor = { new(name: string): { name: string }; }; const Person: PersonConstructor = function(name) { return { name }; }; const p = new Person("Alice");
内容的提问来源于stack exchange,提问作者user1902408

