在 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. 映射类型
映射类型是一种高级类型操作,它可以从现有类型生成新的类型。你可以使用 keyof
和 in
关键字来创建映射类型。
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
或者class
(class
是 JavaScript 中的保留字),就不能写成person.first - name
或person.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.parse
和 JSON.stringify
这是最简单的深拷贝方法,但它只能处理可序列化的对象(不支持函数、undefined
、Symbol
、日期对象、正则表达式等)。
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