Skip to content

TypeScript 对象

在 TypeScript 中,对象是一种复合数据类型,用于存储键值对。对象可以包含属性和方法,是 TypeScript 中最常用的数据结构之一。本文将详细介绍 TypeScript 中的对象。

1. 对象的定义

基本语法

使用对象字面量语法定义对象。

typescript
// 基本对象定义
const person = {
  name: "John",
  age: 30,
  gender: "male"
};

console.log(person.name); // 输出:John
console.log(person.age); // 输出:30
console.log(person.gender); // 输出:male

对象的类型注解

使用类型注解为对象指定类型。

typescript
// 使用类型注解
interface Person {
  name: string;
  age: number;
  gender: string;
}

const person: Person = {
  name: "John",
  age: 30,
  gender: "male"
};

// 使用类型字面量
const person2: { name: string; age: number; gender: string } = {
  name: "Jane",
  age: 25,
  gender: "female"
};

2. 对象的属性

属性访问

使用点号(.)或方括号([])访问对象的属性。

typescript
const person = {
  name: "John",
  age: 30,
  gender: "male"
};

// 使用点号访问
console.log(person.name); // 输出:John

// 使用方括号访问
console.log(person["age"]); // 输出:30

// 使用变量作为属性名
const propertyName = "gender";
console.log(person[propertyName]); // 输出:male

属性修改

可以修改对象的属性值。

typescript
const person = {
  name: "John",
  age: 30,
  gender: "male"
};

// 修改属性值
person.name = "Jane";
person["age"] = 25;

console.log(person.name); // 输出:Jane
console.log(person.age); // 输出:25

属性添加

可以向对象添加新的属性。

typescript
const person = {
  name: "John",
  age: 30
};

// 添加新属性
person.gender = "male";
person["email"] = "john@example.com";

console.log(person.gender); // 输出:male
console.log(person.email); // 输出:john@example.com

属性删除

使用 delete 操作符删除对象的属性。

typescript
const person = {
  name: "John",
  age: 30,
  gender: "male"
};

// 删除属性
delete person.gender;

console.log(person.gender); // 输出:undefined
console.log("gender" in person); // 输出:false

3. 对象的方法

方法定义

对象可以包含方法,方法是对象的函数属性。

typescript
const person = {
  name: "John",
  age: 30,
  greet: function() {
    return `Hello, my name is ${this.name}.`;
  },
  // 箭头函数方法(注意:箭头函数没有自己的 this)
  greetArrow: () => {
    // 这里的 this 不是指向 person 对象
    return "Hello!";
  },
  // 简写方法
  sayAge() {
    return `I am ${this.age} years old.`;
  }
};

console.log(person.greet()); // 输出:Hello, my name is John.
console.log(person.greetArrow()); // 输出:Hello!
console.log(person.sayAge()); // 输出:I am 30 years old.

方法中的 this

在对象方法中,this 指向调用该方法的对象。

typescript
const person = {
  name: "John",
  age: 30,
  greet: function() {
    return `Hello, my name is ${this.name}.`;
  }
};

const anotherPerson = {
  name: "Jane"
};

// 借用方法
const greetFunction = person.greet;
console.log(greetFunction()); // 输出:Hello, my name is undefined.(this 指向全局对象)

// 使用 call 方法设置 this
console.log(greetFunction.call(anotherPerson)); // 输出:Hello, my name is Jane.

// 使用 apply 方法设置 this
console.log(greetFunction.apply(anotherPerson)); // 输出:Hello, my name is Jane.

// 使用 bind 方法绑定 this
const boundGreet = greetFunction.bind(anotherPerson);
console.log(boundGreet()); // 输出:Hello, my name is Jane.

4. 对象字面量

基本语法

对象字面量是创建对象的简洁语法。

typescript
// 基本对象字面量
const person = {
  name: "John",
  age: 30
};

// 计算属性名
const propertyName = "gender";
const person2 = {
  name: "John",
  [propertyName]: "male"
};
console.log(person2.gender); // 输出:male

// 简写属性名
const name = "John";
const age = 30;
const person3 = {
  name,
  age
};
console.log(person3.name); // 输出:John
console.log(person3.age); // 输出:30

// 简写方法
const person4 = {
  name: "John",
  greet() {
    return `Hello, my name is ${this.name}.`;
  }
};
console.log(person4.greet()); // 输出:Hello, my name is John.

对象字面量的类型推断

TypeScript 会根据对象字面量的内容推断其类型。

typescript
// 类型推断为 { name: string; age: number; }
const person = {
  name: "John",
  age: 30
};

// 类型推断为 { name: string; age: number; gender?: string; }
const person2 = {
  name: "John",
  age: 30,
  gender: "male" // 可选属性
};

// 类型推断为 { name: string; age: number; greet: () => string; }
const person3 = {
  name: "John",
  age: 30,
  greet() {
    return `Hello, my name is ${this.name}.`;
  }
};

5. 对象类型

接口

使用接口定义对象类型。

typescript
interface Person {
  name: string;
  age: number;
  gender?: string; // 可选属性
  readonly id: number; // 只读属性
}

const person: Person = {
  id: 1,
  name: "John",
  age: 30
};

// person.id = 2; // 错误:只读属性
person.gender = "male"; // 正确:可选属性

类型别名

使用类型别名定义对象类型。

typescript
type Person = {
  name: string;
  age: number;
  gender?: string;
};

const person: Person = {
  name: "John",
  age: 30,
  gender: "male"
};

索引签名

使用索引签名定义对象的索引类型。

typescript
// 字符串索引签名
interface StringMap {
  [key: string]: string;
}

const map: StringMap = {
  name: "John",
  age: "30"
};

// 数字索引签名
interface NumberArray {
  [index: number]: number;
}

const array: NumberArray = [1, 2, 3, 4, 5];

// 混合索引签名
interface MixedMap {
  [key: string]: string | number;
  length: number;
}

const mixed: MixedMap = {
  name: "John",
  age: 30,
  length: 2
};

6. 对象的解构

基本解构

使用解构语法从对象中提取属性。

typescript
const person = {
  name: "John",
  age: 30,
  gender: "male"
};

// 基本解构
const { name, age } = person;
console.log(name); // 输出:John
console.log(age); // 输出:30

// 重命名变量
const { name: personName, age: personAge } = person;
console.log(personName); // 输出:John
console.log(personAge); // 输出:30

// 默认值
const { name, age, city = "New York" } = person;
console.log(city); // 输出:New York

// 嵌套解构
const personWithAddress = {
  name: "John",
  age: 30,
  address: {
    street: "123 Main St",
    city: "New York"
  }
};

const { address: { street, city } } = personWithAddress;
console.log(street); // 输出:123 Main St
console.log(city); // 输出:New York

函数参数解构

在函数参数中使用解构语法。

typescript
interface Person {
  name: string;
  age: number;
  gender?: string;
}

// 函数参数解构
function greet({ name, age, gender = "unknown" }: Person): string {
  return `Hello, ${name}! You are ${age} years old and your gender is ${gender}.`;
}

const person = {
  name: "John",
  age: 30,
  gender: "male"
};

console.log(greet(person)); // 输出:Hello, John! You are 30 years old and your gender is male.

7. 对象的扩展运算符

扩展对象

使用扩展运算符(...)扩展对象。

typescript
const person = {
  name: "John",
  age: 30
};

// 复制对象
const personCopy = { ...person };
console.log(personCopy); // 输出:{ name: 'John', age: 30 }

// 合并对象
const personWithAddress = {
  ...person,
  address: "123 Main St"
};
console.log(personWithAddress); // 输出:{ name: 'John', age: 30, address: '123 Main St' }

// 覆盖属性
const updatedPerson = {
  ...person,
  age: 31
};
console.log(updatedPerson); // 输出:{ name: 'John', age: 31 }

// 合并多个对象
const person1 = { name: "John" };
const person2 = { age: 30 };
const person3 = { gender: "male" };
const combinedPerson = { ...person1, ...person2, ...person3 };
console.log(combinedPerson); // 输出:{ name: 'John', age: 30, gender: 'male' }

剩余属性

使用剩余运算符(...)收集剩余属性。

typescript
const person = {
  name: "John",
  age: 30,
  gender: "male",
  email: "john@example.com"
};

// 提取部分属性,剩余属性放入 rest
const { name, age, ...rest } = person;
console.log(name); // 输出:John
console.log(age); // 输出:30
console.log(rest); // 输出:{ gender: 'male', email: 'john@example.com' }

8. 对象的类型守卫

typeof 类型守卫

使用 typeof 操作符进行类型守卫。

typescript
interface Person {
  name: string;
  age: number;
}

function processValue(value: string | number | Person) {
  if (typeof value === "string") {
    console.log(`String: ${value}`);
  } else if (typeof value === "number") {
    console.log(`Number: ${value}`);
  } else {
    console.log(`Person: ${value.name}, ${value.age}`);
  }
}

processValue("John"); // 输出:String: John
processValue(30); // 输出:Number: 30
processValue({ name: "John", age: 30 }); // 输出:Person: John, 30

instanceof 类型守卫

使用 instanceof 操作符进行类型守卫。

typescript
class Person {
  constructor(public name: string, public age: number) {}
}

class Animal {
  constructor(public name: string, public species: string) {}
}

function processObject(obj: Person | Animal) {
  if (obj instanceof Person) {
    console.log(`Person: ${obj.name}, ${obj.age}`);
  } else if (obj instanceof Animal) {
    console.log(`Animal: ${obj.name}, ${obj.species}`);
  }
}

processObject(new Person("John", 30)); // 输出:Person: John, 30
processObject(new Animal("Rex", "Dog")); // 输出:Animal: Rex, Dog

in 操作符类型守卫

使用 in 操作符进行类型守卫。

typescript
interface Person {
  name: string;
  age: number;
}

interface Animal {
  name: string;
  species: string;
}

function processObject(obj: Person | Animal) {
  if ("age" in obj) {
    console.log(`Person: ${obj.name}, ${obj.age}`);
  } else if ("species" in obj) {
    console.log(`Animal: ${obj.name}, ${obj.species}`);
  }
}

processObject({ name: "John", age: 30 }); // 输出:Person: John, 30
processObject({ name: "Rex", species: "Dog" }); // 输出:Animal: Rex, Dog

自定义类型守卫

使用自定义类型守卫函数进行类型守卫。

typescript
interface Person {
  name: string;
  age: number;
}

interface Animal {
  name: string;
  species: string;
}

function isPerson(obj: any): obj is Person {
  return typeof obj === "object" && obj !== null && "age" in obj;
}

function isAnimal(obj: any): obj is Animal {
  return typeof obj === "object" && obj !== null && "species" in obj;
}

function processObject(obj: Person | Animal) {
  if (isPerson(obj)) {
    console.log(`Person: ${obj.name}, ${obj.age}`);
  } else if (isAnimal(obj)) {
    console.log(`Animal: ${obj.name}, ${obj.species}`);
  }
}

processObject({ name: "John", age: 30 }); // 输出:Person: John, 30
processObject({ name: "Rex", species: "Dog" }); // 输出:Animal: Rex, Dog

9. 对象的最佳实践

1. 使用接口定义对象类型

使用接口定义对象类型,提高代码的可读性和类型安全性。

typescript
// 推荐:使用接口
interface User {
  id: number;
  name: string;
  email: string;
}

function getUser(id: number): User {
  // 实现
}

// 不推荐:直接使用类型字面量
function getUser(id: number): { id: number; name: string; email: string } {
  // 实现
}

2. 使用可选属性和只读属性

对于可能不存在的属性,使用可选属性;对于不应该被修改的属性,使用只读属性。

typescript
// 推荐:使用可选属性和只读属性
interface User {
  readonly id: number;
  name: string;
  email: string;
  age?: number;
}

// 不推荐:使用普通属性
interface User {
  id: number;
  name: string;
  email: string;
  age: number | undefined;
}

3. 使用解构简化代码

使用解构语法简化代码,提高可读性。

typescript
// 推荐:使用解构
function greet({ name, age }: { name: string; age: number }): string {
  return `Hello, ${name}! You are ${age} years old.`;
}

// 不推荐:不使用解构
function greet(person: { name: string; age: number }): string {
  return `Hello, ${person.name}! You are ${person.age} years old.`;
}

4. 使用扩展运算符合并对象

使用扩展运算符合并对象,简化代码。

typescript
// 推荐:使用扩展运算符
const person = { name: "John", age: 30 };
const updatedPerson = { ...person, age: 31 };

// 不推荐:手动复制属性
const person = { name: "John", age: 30 };
const updatedPerson = { name: person.name, age: 31 };

5. 使用类型守卫处理联合类型

使用类型守卫处理联合类型,提高代码的类型安全性。

typescript
// 推荐:使用类型守卫
function processValue(value: string | number | Person) {
  if (typeof value === "string") {
    // 处理字符串
  } else if (typeof value === "number") {
    // 处理数字
  } else {
    // 处理 Person 对象
  }
}

// 不推荐:不使用类型守卫
function processValue(value: string | number | Person) {
  // 可能会导致类型错误
}

10. 常见错误

1. 属性访问错误

尝试访问不存在的属性会导致 undefined

typescript
const person = {
  name: "John",
  age: 30
};

// 错误:访问不存在的属性
console.log(person.gender); // 输出:undefined

// 正确:使用可选链运算符
console.log(person?.gender); // 输出:undefined

// 正确:使用 in 操作符检查属性是否存在
if ("gender" in person) {
  console.log(person.gender);
}

2. 类型错误

属性类型不匹配会导致类型错误。

typescript
interface Person {
  name: string;
  age: number;
}

// 错误:类型不匹配
// const person: Person = {
//   name: "John",
//   age: "30" // 类型 'string' 不能赋值给类型 'number'
// };

// 正确:类型匹配
const person: Person = {
  name: "John",
  age: 30
};

3. 只读属性修改错误

尝试修改只读属性会导致错误。

typescript
interface Person {
  readonly id: number;
  name: string;
  age: number;
}

const person: Person = {
  id: 1,
  name: "John",
  age: 30
};

// 错误:修改只读属性
// person.id = 2; // 无法分配到 "id" ,因为它是只读属性

// 正确:修改非只读属性
person.name = "Jane";
person.age = 25;

4. this 指向错误

在箭头函数中,this 不指向调用对象。

typescript
const person = {
  name: "John",
  age: 30,
  // 错误:箭头函数中的 this 不是指向 person 对象
  greet: () => {
    return `Hello, my name is ${this.name}.`; // this.name 是 undefined
  },
  // 正确:使用普通函数
  greetCorrect() {
    return `Hello, my name is ${this.name}.`;
  }
};

console.log(person.greet()); // 输出:Hello, my name is undefined.
console.log(person.greetCorrect()); // 输出:Hello, my name is John.

5. 对象解构错误

解构时属性名不匹配会导致 undefined

typescript
const person = {
  name: "John",
  age: 30
};

// 错误:解构时属性名不匹配
const { name, years } = person;
console.log(years); // 输出:undefined

// 正确:解构时使用正确的属性名
const { name, age } = person;
console.log(age); // 输出:30

11. 实际应用场景

1. 定义配置对象

typescript
interface DatabaseConfig {
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
  ssl?: boolean;
  poolSize?: number;
}

const config: DatabaseConfig = {
  host: "localhost",
  port: 5432,
  username: "postgres",
  password: "password",
  database: "mydb",
  ssl: false,
  poolSize: 10
};

function connectToDatabase(config: DatabaseConfig): void {
  console.log(`Connecting to ${config.database} on ${config.host}:${config.port}`);
  // 实现连接逻辑
}

connectToDatabase(config);

2. 处理 API 响应

typescript
interface User {
  id: number;
  name: string;
  email: string;
  createdAt: string;
  updatedAt: string;
}

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();
  return data;
}

fetchUser(1).then((response) => {
  const { data, status, message } = response;
  console.log(`User: ${data.name}, Status: ${status}, Message: ${message}`);
});

3. 实现状态管理

typescript
interface State {
  user: {
    id: number;
    name: string;
    email: string;
  } | null;
  isLoading: boolean;
  error: string | null;
}

const initialState: State = {
  user: null,
  isLoading: false,
  error: null
};

function reducer(state: State, action: any): State {
  switch (action.type) {
    case "FETCH_USER_START":
      return {
        ...state,
        isLoading: true,
        error: null
      };
    case "FETCH_USER_SUCCESS":
      return {
        ...state,
        user: action.payload,
        isLoading: false,
        error: null
      };
    case "FETCH_USER_ERROR":
      return {
        ...state,
        isLoading: false,
        error: action.payload
      };
    default:
      return state;
  }
}

// 使用
let state = initialState;
console.log(state); // 初始状态

state = reducer(state, { type: "FETCH_USER_START" });
console.log(state); // 加载状态

state = reducer(state, { 
  type: "FETCH_USER_SUCCESS", 
  payload: { id: 1, name: "John", email: "john@example.com" } 
});
console.log(state); // 成功状态

4. 实现选项对象

typescript
interface Options {
  timeout?: number;
  retry?: number;
  headers?: Record<string, string>;
  body?: any;
  method?: "GET" | "POST" | "PUT" | "DELETE";
}

function fetchWithOptions(url: string, options: Options = {}): Promise<any> {
  const {
    timeout = 30000,
    retry = 3,
    headers = { "Content-Type": "application/json" },
    body,
    method = "GET"
  } = options;

  console.log(`Fetching ${url} with method ${method}`);
  console.log(`Timeout: ${timeout}ms, Retry: ${retry} times`);
  console.log(`Headers:`, headers);
  console.log(`Body:`, body);

  // 实现 fetch 逻辑
  return fetch(url, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined
  });
}

// 使用
fetchWithOptions("https://api.example.com/users", {
  method: "POST",
  body: { name: "John", email: "john@example.com" },
  timeout: 60000
});

5. 实现工具函数

typescript
interface Person {
  name: string;
  age: number;
  gender: string;
}

function sortByProperty<T>(array: T[], property: keyof T): T[] {
  return array.sort((a, b) => {
    if (a[property] < b[property]) return -1;
    if (a[property] > b[property]) return 1;
    return 0;
  });
}

const people: Person[] = [
  { name: "John", age: 30, gender: "male" },
  { name: "Jane", age: 25, gender: "female" },
  { name: "Bob", age: 35, gender: "male" }
];

// 按年龄排序
const sortedByAge = sortByProperty(people, "age");
console.log(sortedByAge);

// 按名字排序
const sortedByName = sortByProperty(people, "name");
console.log(sortedByName);

总结

TypeScript 中的对象是一种复合数据类型,用于存储键值对。本文介绍了 TypeScript 对象的以下内容:

  • 对象的定义:基本语法,对象的类型注解
  • 对象的属性:属性访问,属性修改,属性添加,属性删除
  • 对象的方法:方法定义,方法中的 this
  • 对象字面量:基本语法,计算属性名,简写属性名,简写方法,对象字面量的类型推断
  • 对象类型:接口,类型别名,索引签名
  • 对象的解构:基本解构,函数参数解构
  • 对象的扩展运算符:扩展对象,剩余属性
  • 对象的类型守卫:typeof 类型守卫,instanceof 类型守卫,in 操作符类型守卫,自定义类型守卫
  • 对象的最佳实践:使用接口定义对象类型,使用可选属性和只读属性,使用解构简化代码,使用扩展运算符合并对象,使用类型守卫处理联合类型
  • 常见错误:属性访问错误,类型错误,只读属性修改错误,this 指向错误,对象解构错误
  • 实际应用场景:定义配置对象,处理 API 响应,实现状态管理,实现选项对象,实现工具函数

通过合理使用对象,你可以在 TypeScript 中构建更加模块化、可维护的代码,提高代码的可读性和可重用性。