TypeScript 数据类型 - 对象

11 阅读8分钟

在 TypeScript 中,对象(Objects)是编程中的核心概念之一,它们用于表示具有属性和方法的实体。TypeScript 为 JavaScript 的对象添加了类型注解和其他静态类型特性,使得对象更加健壮和易于维护。以下是关于 TypeScript 对象的详细介绍,包括对象的基本语法、属性和方法的定义、类型注解、解构赋值、扩展运算符等。

对象的基本定义

1. 创建对象字面量

你可以使用对象字面量来创建一个对象,并为其定义属性和方法。

let person = {
    name: "Alice",
    age: 30,
    greet: function() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
};

person.greet(); // 输出: Hello, my name is Alice and I am 30 years old.
2. 使用类创建对象

你也可以使用类来创建对象,这提供了更结构化的方式来定义对象及其行为。

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

    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

let alice = new Person("Alice", 30);
alice.greet(); // 输出: Hello, my name is Alice and I am 30 years old.

对象的类型注解

1. 基本对象类型注解

你可以为对象定义类型注解,以确保对象具有特定的属性和方法。

type Person = {
    name: string;
    age: number;
    greet: () => void;
};

let person: Person = {
    name: "Alice",
    age: 30,
    greet: function() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
};

person.greet(); // 输出: Hello, my name is Alice and I am 30 years old.
2. 可选属性

你可以使用 ? 来标记对象中的某些属性为可选。

type Person = {
    name: string;
    age?: number; // 可选属性
    greet: () => void;
};

let person: Person = {
    name: "Alice",
    greet: function() {
        console.log(`Hello, my name is ${this.name}`);
    }
};

person.greet(); // 输出: Hello, my name is Alice
3. 只读属性

你可以使用 readonly 关键字来定义只读属性,这些属性在初始化后不能被修改。

type Person = {
    readonly name: string;
    age: number;
    greet: () => void;
};

let person: Person = {
    name: "Alice",
    age: 30,
    greet: function() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
};

person.age = 31; // OK
// person.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property.
4. 索引签名

索引签名允许你定义对象中所有属性的类型,而不需要显式列出每个属性。

type StringMap = {
    [key: string]: string;
};

let map: StringMap = {
    name: "Alice",
    age: "30"
};

console.log(map.name); // 输出: Alice
console.log(map.age); // 输出: 30

对象的解构赋值

解构赋值允许你从对象中提取属性并将其赋值给变量。

1. 基本解构
let person = {
    name: "Alice",
    age: 30
};

let { name, age } = person;

console.log(name); // 输出: Alice
console.log(age); // 输出: 30
2. 重命名解构

你可以通过指定别名来重命名解构的属性。

let person = {
    name: "Alice",
    age: 30
};

let { name: userName, age: userAge } = person;

console.log(userName); // 输出: Alice
console.log(userAge); // 输出: 30
3. 默认值

你可以为解构的属性提供默认值,以防对象中没有该属性。

let person = {
    name: "Alice"
};

let { name, age = 18 } = person;

console.log(name); // 输出: Alice
console.log(age); // 输出: 18

扩展运算符

扩展运算符(...)允许你将一个对象的属性复制到另一个对象中,或者将多个对象合并为一个新的对象。

1. 复制对象属性
let person = {
    name: "Alice",
    age: 30
};

let clonedPerson = { ...person };

console.log(clonedPerson); // 输出: { name: 'Alice', age: 30 }
2. 合并对象
let person = {
    name: "Alice",
    age: 30
};

let address = {
    city: "New York",
    country: "USA"
};

let fullPerson = { ...person, ...address };

console.log(fullPerson); // 输出: { name: 'Alice', age: 30, city: 'New York', country: 'USA' }

对象的方法

对象可以包含方法,这些方法是函数,用于操作对象的状态或执行某些操作。

let person = {
    name: "Alice",
    age: 30,
    greet: function() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
};

person.greet(); // 输出: Hello, my name is Alice and I am 30 years old.

对象的迭代

你可以使用 for...in 循环来遍历对象的属性。

let person = {
    name: "Alice",
    age: 30,
    city: "New York"
};

for (let key in person) {
    console.log(`${key}: ${person[key]}`);
}

// 输出:
// name: Alice
// age: 30
// city: New York

对象的高级用法

1. 计算属性名

你可以使用方括号语法来动态计算对象的属性名。

let propertyName = "name";
let person = {
    [propertyName]: "Alice",
    age: 30
};

console.log(person.name); // 输出: Alice
2. 类型断言

有时你需要告诉 TypeScript 某个对象的实际类型,这时可以使用类型断言。

let obj = { name: "Alice" };
let person = obj as { name: string, age: number };

person.name = "Bob"; // OK
// person.age = 30; // Error: Property 'age' does not exist on type '{ name: string; age: number; }'.
3. 映射类型

映射类型是一种高级类型操作,它可以从现有类型生成新的类型。你可以使用 keyofin 关键字来创建映射类型。

type Keys = "option1" | "option2";
interface Options {
    [K in Keys]: boolean;
}

let config: Options = {
    option1: true,
    option2: false
};

总结

TypeScript 的对象功能非常强大,提供了丰富的类型注解、解构赋值、扩展运算符以及其他静态类型特性。通过使用这些特性,你可以编写更加结构化、模块化和可维护的代码。掌握对象的基本语法、类型注解、解构赋值以及与其他类型系统的结合使用,可以帮助你在开发中编写出更健壮、更灵活的应用程序。了解如何使用对象的高级特性,如计算属性名、类型断言和映射类型,可以使你的代码更加简洁和高效。

创建对象

对象字面量

const person = {
  name: "peter",
  age: 10,
};
console.log(person); // { name: 'peter', age: 10 }

Object 构造函数

const myObj = new Object();
myObj.key1 = "value1";
myObj.key2 = "value2";
console.log(myObj); // { key1: 'value1', key2: 'value2' }

添加元素

直接赋值

const myObj = {};
myObj["a"] = 1;
myObj.b = 2;
console.log(myObj); // { a: 1, b: 2 }

删除元素

删除指定元素 delete

const myObj = { a: 1, b: 2 };
delete myObj.a;
console.log("a" in myObj); // false

清空元素

const myObj = { a: 1, b: 2 };
for (let key in myObj) {
  delete myObj[key];
}
console.log(Object.keys(myObj).length); // 0

访问和查找元素

点(.)表示法

let person = {
  name: "John",
  age: 30,
};
console.log(person.name); // 输出 'John'
console.log(person.age); // 输出 30

局限性:当属性名包含特殊字符(如空格、连字符等)或者是一个保留字时,不能直接使用点表示法。例如,如果有一个属性名为first - name或者classclass是 JavaScript 中的保留字),就不能写成person.first - nameperson.class,因为这样会被 JavaScript 解析器误解为减法运算或其他语法错误。

方括号([])表示法

适用于属性名不符合标识符规则或者属性名是变量的情况。

let person = {
  "first - name": "John",
  age: 30,
};
console.log(person["first - name"]); // 输出 'John'
let propertyName = "age";
console.log(person[propertyName]); // 输出 30

动态属性访问。例如,在一个函数中,根据用户输入或者程序的逻辑来决定访问哪个属性。

function getPropertyValue(obj, property) {
  return obj[property];
}
let person = {
  name: "Alice",
  city: "New York",
};
let propToAccess = "city";
console.log(getPropertyValue(person, propToAccess)); // 输出 'New York'

不存在的属性

const obj = { name: "Alice" };
console.log(obj.age); // 输出: undefined
console.log(obj["age"]); // 输出: undefined

访问不存在的属性会抛出错误的情况

const obj = { name: "Alice" };
// console.log(obj.address.street); // TypeError: Cannot read properties of undefined (reading 'street')

为避免这种情况,可以使用可选链(optional chaining)运算符(?.):

const obj = { name: "Alice" };
console.log(obj.address?.street); // 输出: undefined

在访问 JavaScript 对象中不存在的属性时,通常返回 undefined。你可以使用 in 操作符或 hasOwnProperty 方法来检查属性的存在性,而可选链运算符可以防止访问嵌套对象时发生错误。

遍历对象

for...in 循环

for...in 循环主要是设计用来遍历对象的可枚举属性。

从语法上来说,for...in 也可以用于数组。但是它遍历的是数组的索引(作为字符串),而不是数组元素的值。而且,它会遍历数组对象的所有可枚举属性,包括那些由原型链继承而来的属性。

对于 Set、Map 不会按照期望输出,而是可能输出一些无关的对象内置属性。

const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key + ": " + obj[key]);
  }
}

Object.keys

const obj = { a: 1, b: 2, c: 3 };
const keys = Object.keys(obj);
for (const key of keys) {
  console.log(key + ": " + obj[key]);
}

Object.values

const values = Object.values(obj);
for (const value of values) {
  console.log(value);
}

Object.entries

const entries = Object.entries(obj);
for (const [key, value] of entries) {
  console.log(key + ": " + value);
}

其他

是否存在属性

in 操作符

in 操作符可以检查对象是否具有指定属性。即使属性不存在,它也会返回 false

const myObj = { a: 1 };
console.log("a" in myObj); // true
console.log("b" in myObj); // false

hasOwnProperty

通过 hasOwnProperty 方法可以检查对象是否直接拥有某个属性,结果同样为 false

const obj = { name: "Alice" };
console.log(obj.hasOwnProperty("age")); // 输出: false
console.log(obj.hasOwnProperty("name")); // 输出: true

拷贝

浅拷贝

const copyD = { ...d };

深拷贝

在 JavaScript 中,深拷贝指的是创建一个对象的完整副本,包括其所有嵌套的对象和数组。以下是一些常见的方法来实现深拷贝。

1. 使用 JSON.parseJSON.stringify

这是最简单的深拷贝方法,但它只能处理可序列化的对象(不支持函数、undefinedSymbol、日期对象、正则表达式等)。

const original = {
  name: "Alice",
  age: 30,
  hobbies: ["reading", "travelling"],
  address: {
    city: "New York",
    zip: 10001,
  },
};

const deepCopy = JSON.parse(JSON.stringify(original));

console.log(deepCopy); // 深拷贝的对象

2. 使用递归方法

可以手动实现深拷贝,适用于更复杂的对象:

function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj; // 基本数据类型直接返回
  }

  // 处理数组
  if (Array.isArray(obj)) {
    return obj.map((item) => deepClone(item));
  }

  // 处理对象
  const copy = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key]);
    }
  }
  return copy;
}

const original = {
  name: "Alice",
  age: 30,
  hobbies: ["reading", "travelling"],
  address: {
    city: "New York",
    zip: 10001,
  },
};

const deepCopy = deepClone(original);
console.log(deepCopy);

3. 使用第三方库

如果你需要处理更复杂的情况,可以使用第三方库,如 Lodash 的 cloneDeep 方法。

npm install lodash
const _ = require("lodash");

const original = {
  name: "Alice",
  age: 30,
  hobbies: ["reading", "travelling"],
  address: {
    city: "New York",
    zip: 10001,
  },
};

const deepCopy = _.cloneDeep(original);
console.log(deepCopy);

排序

键排序

// 创建一个对象字面量
const originalObject = {
  key3: 3,
  key1: 1,
  key2: 2,
};

// 将对象的键值对转换为数组并排序
const sortedEntries = Object.entries(originalObject).sort((a, b) =>
  a[0].localeCompare(b[0])
);

// 输出排序后的结果
console.log("排序后的数组:", sortedEntries); // 输出: [ [ 'key1', 1 ], [ 'key2', 2 ], [ 'key3', 3 ] ]

// 如果需要,可以将排序后的数组转换回对象
const sortedObject = Object.fromEntries(sortedEntries);
console.log("排序后的对象:", sortedObject); // 输出: { key1: 1, key2: 2, key3: 3 }

值排序

// 创建一个对象字面量
const originalObject = {
  key3: 3,
  key1: 1,
  key2: 2,
};

// 将对象的键值对转换为数组,并根据值进行排序
const sortedEntries = Object.entries(originalObject).sort(
  (a, b) => a[1] - b[1]
);

// 输出排序后的键值对数组
console.log("排序后的数组:", sortedEntries); // 输出: [ [ 'key1', 1 ], [ 'key2', 2 ], [ 'key3', 3 ] ]

// 如果需要,可以将排序后的数组转换回对象
const sortedObject = Object.fromEntries(sortedEntries);
console.log("排序后的对象:", sortedObject); // 输出: { key1: 1, key2: 2, key3: 3 }

对象数组排序

// 创建对象数组
const arr = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 30 },
  { name: "Charlie", age: 20 },
];

// 根据 age 属性进行排序
arr.sort((a, b) => a.age - b.age);

// 输出排序后的数组
console.log(arr);
// 输出: [ { name: 'Charlie', age: 20 }, { name: 'Alice', age: 25 }, { name: 'Bob', age: 30 } ]

反转

// 创建一个对象字面量
const originalObject = {
  key1: 1,
  key2: 2,
  key3: 3,
};

// 将对象的键值对转换为数组并反转
const reversedEntries = Object.entries(originalObject).reverse();

// 输出反转后的结果
console.log("反转后的数组:", reversedEntries); // 输出: [ [ 'key3', 3 ], [ 'key2', 2 ], [ 'key1', 1 ] ]

// 如果需要,可以将反转后的数组转换回对象
const reversedObject = Object.fromEntries(reversedEntries);
console.log("反转后的对象:", reversedObject); // 输出: { key3: 3, key2: 2, key1: 1 }

属性

长度

对象没有内置的 length 属性,但可以使用 Object.keys() 计算属性数量:

const myObj = { a: 1, b: 2 };
console.log(Object.keys(myObj).length); // 2