TypeScript 数据类型 - 映射

123 阅读7分钟

Map 是 TypeScript 和 JavaScript 中的一种内置对象,它用于存储键值对(key-value pairs),并且可以记住键的原始插入顺序。与传统的对象字面量相比,Map 提供了更多的功能和灵活性,尤其是在处理非字符串键和动态添加或删除键值对时。以下是关于 Map 的详细介绍,包括基本用法、常用方法、类型注解以及与其他数据结构的比较。

基本用法

1. 创建一个 Map

你可以使用 new Map() 来创建一个新的 Map 实例。

let map = new Map();
2. 添加键值对

你可以使用 set 方法来向 Map 中添加键值对。

let map = new Map();
map.set("name", "Alice");
map.set("age", 30);
map.set(true, "This is true");

console.log(map); // Map(3) { 'name' => 'Alice', 'age' => 30, true => 'This is true' }
3. 获取值

你可以使用 get 方法来获取 Map 中的值。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

console.log(map.get("name")); // 输出: Alice
console.log(map.get("age"));  // 输出: 30
4. 检查键是否存在

你可以使用 has 方法来检查 Map 中是否包含某个键。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

console.log(map.has("name")); // 输出: true
console.log(map.has("city")); // 输出: false
5. 删除键值对

你可以使用 delete 方法来删除 Map 中的键值对。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

map.delete("age");

console.log(map); // Map(1) { 'name' => 'Alice' }
6. 清空 Map

你可以使用 clear 方法来清空 Map 中的所有键值对。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

map.clear();

console.log(map); // Map(0) {}

常用方法

1. size

size 属性返回 Map 中键值对的数量。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

console.log(map.size); // 输出: 2
2. forEach

forEach 方法允许你遍历 Map 中的每个键值对。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

map.forEach((value, key) => {
    console.log(`${key}: ${value}`);
});

// 输出:
// name: Alice
// age: 30
3. keys

keys 方法返回一个迭代器,用于遍历 Map 中的所有键。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

for (let key of map.keys()) {
    console.log(key);
}

// 输出:
// name
// age
4. values

values 方法返回一个迭代器,用于遍历 Map 中的所有值。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

for (let value of map.values()) {
    console.log(value);
}

// 输出:
// Alice
// 30
5. entries

entries 方法返回一个迭代器,用于遍历 Map 中的所有键值对。

let map = new Map([
    ["name", "Alice"],
    ["age", 30]
]);

for (let [key, value] of map.entries()) {
    console.log(`${key}: ${value}`);
}

// 输出:
// name: Alice
// age: 30

类型注解

TypeScript 允许你为 Map 的键和值添加类型注解,以确保类型安全。

let stringNumberMap: Map<string, number> = new Map();
stringNumberMap.set("one", 1);
stringNumberMap.set("two", 2);

let anyMap: Map<any, any> = new Map();
anyMap.set("name", "Alice");
anyMap.set(true, "This is true");
anyMap.set(42, "The answer");

// 使用泛型定义更复杂的类型
type Person = { name: string; age: number };
let personMap: Map<string, Person> = new Map();
personMap.set("alice", { name: "Alice", age: 30 });
personMap.set("bob", { name: "Bob", age: 25 });

console.log(personMap.get("alice")); // 输出: { name: 'Alice', age: 30 }

与对象字面量的比较

1. 键的类型
  • 对象字面量:键只能是字符串或符号(Symbol)。
  • Map:键可以是任何类型的值,包括对象、函数、数字等。
let obj = {};
obj[true] = "This is true"; // 不推荐,虽然可以工作,但不符合预期
obj[42] = "The answer";     // 不推荐,虽然可以工作,但不符合预期

let map = new Map();
map.set(true, "This is true"); // 推荐
map.set(42, "The answer");     // 推荐
2. 键的顺序
  • 对象字面量:键的顺序不保证,特别是对于数字键。
  • Map:键的顺序是按照插入顺序保存的。
let obj = {
    b: "second",
    a: "first"
};

for (let key in obj) {
    console.log(key); // 输出顺序不确定,可能是 "b", "a" 或 "a", "b"
}

let map = new Map([
    ["b", "second"],
    ["a", "first"]
]);

for (let [key, value] of map) {
    console.log(key); // 输出: "b", "a" (按插入顺序)
}
3. 动态添加和删除键
  • 对象字面量:需要手动管理属性的添加和删除。
  • Map:提供了 setgethasdelete 等方法,操作更加方便。
let obj = {};
obj.name = "Alice";
delete obj.name;

let map = new Map();
map.set("name", "Alice");
map.delete("name");
4. 性能
  • 对象字面量:对于小规模的数据集,性能差异不大。
  • Map:对于大规模的数据集,Map 的性能通常优于对象字面量,特别是在频繁添加和删除键的情况下。

高级用法

1. 使用 Map 作为缓存

Map 可以用于实现简单的缓存机制,特别是在需要根据不同的输入参数返回不同结果的情况下。

function fibonacci(n: number, cache: Map<number, number> = new Map()): number {
    if (cache.has(n)) {
        return cache.get(n)!;
    }

    if (n <= 1) {
        return n;
    }

    const result = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);
    cache.set(n, result);
    return result;
}

console.log(fibonacci(10)); // 输出: 55
2. 使用 Map 作为集合

虽然 Set 是专门用于存储唯一值的数据结构,但 Map 也可以用于类似的目的,特别是当你需要存储键值对时。

let uniqueItems = new Map();

uniqueItems.set("apple", 1);
uniqueItems.set("banana", 1);
uniqueItems.set("apple", 1); // 重复的键不会添加新的项

console.log(uniqueItems.size); // 输出: 2

总结

Map 是 TypeScript 和 JavaScript 中非常强大的数据结构,适用于需要存储键值对的场景。与传统的对象字面量相比,Map 提供了更多的灵活性和功能,支持任意类型的键、保持插入顺序、提供丰富的操作方法,并且在性能上也有优势。掌握 Map 的基本用法、常用方法、类型注解以及高级用法,可以帮助你在开发中编写出更健壮、更灵活的应用程序。了解如何在适当的情况下选择 Map 而不是对象字面量,可以使你的代码更加简洁和高效。

Map 是一种内置对象,它类似于对象,有以下区别。

  • 但“键”的范围不限于字符串。任何值(包括对象)都可以作为一个键。
  • Map 保持键值对的原始插入顺序。

创建映射

使用构造函数创建

let map = new Map();

使用数组初始化

const myMap = new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
]);
console.log(myMap);

字面量语法

const myMap = new Map({
  key1: 'value1',
  key2: 'value2'
});
console.log(myMap);

添加元素

set

const myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
console.log(myMap); // Map { 'a' => 1, 'b' => 2 }

删除元素

删除指定元素 delete

const myMap = new Map();
myMap.set('a', 1);
myMap.delete('a');
console.log(myMap.has('a')); // false

清空元素 clear

const myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
myMap.clear();
console.log(myMap.size); // 0

访问和查找元素

get

const myMap = new Map();
myMap.set('a', 1);
console.log(myMap.get('a')); // 1
console.log(myMap.get('b')); // undefined

遍历元素

forEach

const myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
myMap.forEach((value, key) => {
    console.log(`${key}: ${value}`); // a: 1, b: 2
});

for...of 循环

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const [key, value] of map) {
    console.log(key + ': ' + value);
}

keys

const myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
const keysIterator = myMap.keys();
console.log([...keysIterator]); // [ 'a', 'b' ]

values

const myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
const valuesIterator = myMap.values();
console.log([...valuesIterator]); // [ 1, 2 ]

entries

const myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
const entriesIterator = myMap.entries();
console.log([...entriesIterator]); // [ [ 'a', 1 ], [ 'b', 2 ] ]

其他

has

const myMap = new Map();
myMap.set('a', 1);
console.log(myMap.has('a')); // true
console.log(myMap.has('b')); // false

拷贝

浅拷贝

使用构造函数
// 创建原始映射,其中包含一个对象作为值
const originalMap = new Map([
    ['key1', 'value1'],
    ['key2', { a: 3, b: [4, 5] }]
]);

// 创建浅拷贝映射
const shallowCopyMap = new Map(originalMap);

// 输出原始映射和浅拷贝映射
console.log('原始映射:', originalMap); // Map { 'key1' => 'value1', 'key2' => { a: 3, b: [ 4, 5 ] } }
console.log('浅拷贝映射:', shallowCopyMap); // Map { 'key1' => 'value1', 'key2' => { a: 3, b: [ 4, 5 ] } }

// 修改原始映射中的对象
const originalObject = originalMap.get('key2'); // 获取映射中的对象
originalObject.a = 10; // 修改对象的属性

// 输出修改后的原始映射和浅拷贝映射
console.log('修改后的原始映射:', originalMap); // Map { 'key1' => 'value1', 'key2' => { a: 10, b: [ 4, 5 ] } }
console.log('浅拷贝映射:', shallowCopyMap); // Map { 'key1' => 'value1', 'key2' => { a: 10, b: [ 4, 5 ] } }

深拷贝

使用循环和递归方法
function deepCloneMap(map) {
    const newMap = new Map();
    map.forEach((value, key) => {
        newMap.set(key, value instanceof Object ? structuredClone(value) : value);
    });
    return newMap;
}

const originalMap = new Map([['key1', 'value1'], ['key2', { a: 3 }]]);
const deepCopyMap = deepCloneMap(originalMap);
使用 Lodash
const _ = require('lodash');

// 创建原始映射
const originalMap = new Map([
    ['key1', 'value1'],
    ['key2', { a: 3, b: [4, 5] }]
]);

// 深拷贝映射
const deepCopyMap = new Map(_.cloneDeep(Array.from(originalMap.entries())));

console.log(deepCopyMap); // 输出: Map { 'key1' => 'value1', 'key2' => { a: 3, b: [ 4, 5 ] } }

// 修改原始对象
const originalObject = originalMap.get('key2'); // 获取映射中的对象
originalObject.a = 10;

console.log(originalMap);   // 输出: Map { 'key1' => 'value1', 'key2' => { a: 10, b: [ 4, 5 ] } }
console.log(deepCopyMap);   // 输出: Map { 'key1' => 'value1', 'key2' => { a: 3, b: [ 4, 5 ] } } - 深拷贝不受影响

排序

// 创建一个 Map
const originalMap = new Map([
    ['key1', 5],
    ['key2', 3],
    ['key3', 8],
    ['key4', 1]
]);

// 将 Map 转换为数组并排序
const sortedArray = Array.from(originalMap.entries()).sort((a, b) => a[1] - b[1]);

// 输出排序后的结果
console.log('排序后的数组:', sortedArray); // 输出: [ [ 'key4', 1 ], [ 'key2', 3 ], [ 'key1', 5 ], [ 'key3', 8 ] ]

// 如果需要,可以将排序后的数组转换回 Map
const sortedMap = new Map(sortedArray);
console.log('排序后的 Map:', sortedMap);
// 输出: Map { 'key4' => 1, 'key2' => 3, 'key1' => 5, 'key3' => 8 }

反转

// 创建一个 Map
const originalMap = new Map([
    ['key1', 1],
    ['key2', 2],
    ['key3', 3],
]);

// 将 Map 转换为数组并反转
const reversedArray = Array.from(originalMap.entries()).reverse();

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

// 如果需要,可以将反转后的数组转换回 Map
const reversedMap = new Map(reversedArray);
console.log('反转后的 Map:', reversedMap);
// 输出: Map { 'key3' => 3, 'key2' => 2, 'key1' => 1 }

属性

长度 size

返回 Map 中键值对的数量。

const myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
console.log(myMap.size); // 2