Skip to content

TypeScript - 对象

对象 (Objects) 是 JavaScript 和 TypeScript 中用于组织相关数据和功能的基石。它们表示键值对 (key-value pairs) 的集合,其中键 (keys) 通常是字符串 (strings)(或 Symbol),值 (values) 可以是任何类型:基本类型 (primitives)(字符串、数字、布尔值)、函数(方法 methods)、数组,甚至其他对象。

创建对象最常用的方式是使用对象字面量 (object literal) 语法 {}。

// 对象字面量语法
const car = {
make: "Toyota",
model: "Camry",
year: 2022,
start: function() { // 使用传统函数语法的方法
console.log(`Starting the ${this.make} ${this.model}...`);
},
stop() { // 简写方法语法 (ES6)
console.log(`Stopping the ${this.make} ${this.model}.`);
},
features: ["Air Conditioning", "Cruise Control"] // 值可以是数组
};
// 使用点符号访问属性
console.log(car.make); // Output: Toyota
console.log(car.year); // Output: 2202
// 使用方括号符号访问属性(适用于动态键)
const propertyName = "model";
console.log(car[propertyName]); // Output: Camry
// 调用方法
car.start();
car.stop();
console.log(car.features[0]); // Output: Air Conditioning

TypeScript 允许你定义对象的形状或结构,确保在访问属性或方法时具有类型安全性 (type safety)。有几种为对象定义类型的方式:

当你创建一个对象字面量并将其赋值给一个变量时,TypeScript 会推断 (infer) 其属性的类型。

const inferredUser = {
id: 101,
username: "alice",
isActive: true
};
// TypeScript 推断出的类型为: { id: number; username: string; isActive: boolean; }
console.log(inferredUser.username.toUpperCase()); // 正确
// inferredUser.id = "user-101"; // 错误: 类型 'string' 不能赋值给类型 'number'。

使用 interface 是定义对象结构的常见且惯用的方式。

interface Product {
readonly sku: string;
name: string;
price: number;
description?: string; // 可选属性
display(): void;
}
const book: Product = {
sku: "BK-123",
name: "The TypeScript Handbook",
price: 29.99,
// description 是可选的
display() {
console.log(`${this.name} ($${this.price}) - SKU: ${this.sku}`);
}
};
book.display();
// book.sku = "BK-456"; // 错误: 无法赋值到 'sku',因为它是一个只读属性。

使用 type 别名是另一种定义对象形状的方式。

type Point = {
x: number;
y: number;
};
const origin: Point = { x: 0, y: 0 };
function calculateDistance(p1: Point, p2: Point): number {
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
return Math.sqrt(dx * dx + dy * dy);
}
const point1: Point = { x: 3, y: 4 };
console.log(`Distance from origin: ${calculateDistance(origin, point1)}`); // Output: 5

你可以直接在类型注解 (type annotations) 中定义对象形状,这常用于函数参数或返回值类型,特别是不需要可复用的接口或类型时。

function processCoords(coords: { lat: number; lon: number }): void {
console.log(`Latitude: ${coords.lat}, Longitude: ${coords.lon}`);
}
processCoords({ lat: 40.7128, lon: -74.0060 });
// processCoords({ latitude: 40.7, longitude: -74 }); // 错误: 对象字面量只能指定已知属性...

将对象作为参数传递非常常见。使用接口、类型别名或匿名类型来定义参数对象的预期结构。

interface UserProfile {
userId: string;
email: string;
displayName?: string;
}
function displayUserProfile(profile: UserProfile): void {
console.log(`User ID: ${profile.userId}`);
console.log(`Email: ${profile.email}`);
if (profile.displayName) {
console.log(`Display Name: ${profile.displayName}`);
}
}
const user: UserProfile = { userId: 'u-001', email: 'user@example.com', displayName: 'Test User' };
const userWithoutDisplayName: UserProfile = { userId: 'u-002', email: 'another@example.com' };
displayUserProfile(user);
displayUserProfile(userWithoutDisplayName);

TypeScript 使用结构化类型系统 (structural type system)。这意味着如果一个对象至少拥有某个类型(如接口)所要求的属性和方法,TypeScript 就认为它与该类型兼容,无论它是否显式地 implements 了该接口。这通常被称为“鸭子类型 (duck typing)”。

“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子。”

interface Named {
name: string;
}
let obj1 = { name: "My Object", value: 10 }; // 拥有 'name' 属性
let obj2 = { title: "Another Object", name: "Named Item" }; // 拥有 'name' 属性
let obj3 = { id: 123 }; // 没有 'name' 属性
function printName(item: Named): void {
console.log(`Name: ${item.name}`);
}
printName(obj1); // 正确 - obj1 拥有 'name: string' 属性
printName(obj2); // 正确 - obj2 拥有 'name: string' 属性
// printName(obj3); // 错误: 类型 '{ id: number; }' 不能赋值给类型 'Named' 的参数。缺少属性 'name'...

这种灵活性使得不同对象结构之间可以相互操作,只要它们共享所需的形状。