1. object 类型 (小写 o)
- 含义: object 类型代表任何非原始类型 (non-primitive type) 的值。 在项目中一般不用object来类型注解,没有太多作用,还不如类型自动推导 。且object可以表示能用对象创建的函数,数组等。
自动类型推导
使用object
-
哪些属于 object 类型:
- 普通对象字面量 ({})
- 数组 ([])
- 函数 (() => {})
- 类的实例 (new Date(), new RegExp(), 自定义类的实例等)
-
哪些不属于 object 类型 (原始类型) :
- string
- number
- boolean
- symbol
- bigint
- null
- undefined
-
用途和限制:
- 它的主要用途是约束一个值不能是原始类型。
- 它本身非常宽泛,你不能安全地访问一个被注解为 object 类型的变量的任何属性,因为 TypeScript 不知道这个对象的具体“形状”。
let obj: object;
obj = { name: "Alice" }; // OK
obj = [1, 2, 3]; // OK
obj = () => "hello"; // OK
obj = new Date(); // OK
// obj = "hello"; // Error: Type 'string' is not assignable to type 'object'.
// obj = 42; // Error: Type 'number' is not assignable to type 'object'.
// obj = true; // Error: Type 'boolean' is not assignable to type 'object'.
// obj = null; // Error: Type 'null' is not assignable to type 'object'. (需要 strictNullChecks: true)
// obj = undefined; // Error: Type 'undefined' is not assignable to type 'object'. (需要 strictNullChecks: true)
// 访问属性是不安全的,因为不知道具体结构
// console.log(obj.name); // Error: Property 'name' does not exist on type 'object'.
结论: object 类型本身在实践中用途有限,因为它无法描述对象的具体结构。通常我们需要更精确的类型。
2. 更具体的对象类型定义方式
当我们需要描述一个对象应该包含哪些属性以及这些属性的类型时,我们通常使用以下方式:
-
对象字面量类型 (Object Literal Types / Inline Types) : 直接在类型注解中描述对象的结构。
let person: { name: string; age: number; isActive?: boolean }; // ? 表示可选属性 person = { name: "Bob", age: 30 }; // OK person = { name: "Charlie", age: 25, isActive: true }; // OK // person = { name: "David" }; // Error: Property 'age' is missing... // person = { name: "Eve", age: 40, location: "City" }; // Error: Object literal may only specify known properties... (赋值时有额外属性检查) function greet(user: { name: string; email: string }): void { console.log(`Hello ${user.name}, contact: ${user.email}`); } greet({ name: "Frank", email: "frank@example.com" });这种方式适用于结构简单、不需要复用的场景。
-
接口 (Interfaces) : 使用 interface 关键字定义一个命名的对象结构契约。这是定义对象形状最常用、最推荐的方式之一。
interface Point { readonly x: number; // readonly 表示只读 y: number; } interface UserProfile { userId: number | string; username: string; email?: string; // 可选 logins: Date[]; greet(): string; // 方法签名 } let p1: Point = { x: 10, y: 20 }; // p1.x = 5; // Error: Cannot assign to 'x' because it is a read-only property. let user1: UserProfile = { userId: 123, username: "Grace", logins: [new Date()], greet() { return `Hi, I am ${this.username}`; } };优点: 可复用、可读性强、支持继承 (extends)、可被类实现 (implements)、支持声明合并。
-
类型别名 (Type Aliases) : 使用 type 关键字给一个类型(包括对象类型)起一个新名字。也非常常用。
type Coordinates = { lat: number; lon: number; }; type ID = string | number; type PersonData = { id: ID; name: string; coords?: Coordinates; // 可以嵌套使用类型别名 }; let loc: Coordinates = { lat: 34.05, lon: -118.24 }; let personData: PersonData = { id: "p-456", name: "Henry", coords: loc };优点: 可复用、可读性强,不仅能定义对象形状,还能定义联合类型、交叉类型、元组等任何类型。缺点: 不能像接口那样进行声明合并或被 implements (虽然类可以实现由类型别名定义的对象结构)。
-
类 (Classes) : 类本身定义了一个类型,即该类的实例类型。
class Car { make: string; model: string; year: number; constructor(make: string, model: string, year: number) { this.make = make; this.model = model; this.year = year; } displayInfo(): string { return `${this.year} ${this.make} ${this.model}`; } } let myCar: Car = new Car("Toyota", "Camry", 2023); console.log(myCar.displayInfo());
3. 容易混淆的概念:object vs Object vs {}
-
object (小写) : 如上所述,是 TypeScript 的类型,代表非原始值。
-
Object (大写) :
- 是 JavaScript 全局内置的 Object 构造函数(一个值)。
- 也是一个类型,表示 JavaScript 的 Object 构造函数创建的实例的类型。它包含所有对象共享的方法,如 toString(), hasOwnProperty() 等(这些方法实际在 Object.prototype 上)。
- 通常不推荐使用 Object 作为类型注解(let obj: Object),因为它过于宽泛,几乎允许任何值(除了 null 和 undefined,在 strictNullChecks 下),并且无法提供有用的结构信息。你应该使用更具体的接口、类型别名,或者在需要表示“任何非原始值”时使用 object。
-
{} (空对象类型) :
- 表示一个没有任何自身属性的对象类型。
- 由于 TypeScript 的结构化类型系统,几乎所有非 null 和非 undefined 的值都可以赋值给 {} 类型的变量,因为它们满足“不缺少 {} 所需的任何属性”(即没有必需属性)这一条件。
- 因此,{} 不是一个用来表示“任意对象”的好类型,因为它允许原始类型(除了 null/undefined),并且没有提供任何约束。object 类型是更好的选择来表示“任意非原始值”。Record<string, unknown> 或 Record<string, any> (谨慎使用 any) 更适合表示“任意有属性的对象”(字典类型)。
let valObject: Object = { a: 1 }; // 不推荐
valObject = "string"; // OK,因为 string 也有 toString 等方法
valObject = 42; // OK
// valObject = null; // Error (strictNullChecks)
// valObject = undefined; // Error (strictNullChecks)
let valEmpty: {} = { b: 2 }; // OK
valEmpty = "string"; // OK
valEmpty = 42; // OK
valEmpty = () => {}; // OK
// valEmpty = null; // Error (strictNullChecks)
// valEmpty = undefined; // Error (strictNullChecks)
// console.log(valEmpty.b); // Error: Property 'b' does not exist on type '{}'.
let valObjLowercase: object = { c: 3 }; // 更推荐表示非原始值
// valObjLowercase = "string"; // Error: Type 'string' is not assignable to type 'object'.
// valObjLowercase = 42; // Error: Type 'number' is not assignable to type 'object'.
// console.log(valObjLowercase.c); // Error: Property 'c' does not exist on type 'object'.
总结与推荐:
- 需要表示任意非原始值时,使用 object。
- 需要表示具体结构的对象时,优先使用 interface 或 type (类型别名)。
- 需要表示类的实例时,直接使用类名作为类型。
- 需要表示“任意有属性的对象”或“字典”时,考虑 Record<string, unknown> (更安全) 或 Record<string, any> (如果确实需要 any 的灵活性)。
- 避免使用 Object (大写) 和 {} 作为通用对象类型注解。