TS 对象 和 object的基本使用

301 阅读4分钟

1. object 类型 (小写 o)

  • 含义: object 类型代表任何非原始类型 (non-primitive type) 的值。 在项目中一般不用object来类型注解,没有太多作用,还不如类型自动推导 。且object可以表示能用对象创建的函数,数组等。

自动类型推导

Snipaste_2025-04-18_22-56-40.png

使用object

Snipaste_2025-04-18_22-57-53.png

  • 哪些属于 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 (大写) 和 {} 作为通用对象类型注解。