JavaScript中Map对象研究:Map基本介绍、Map()构造函数、实例方法clear、delete、get、set、has、entries、keys、values、forEach
在现代JavaScript开发中,Map
对象作为一种键值对集合,提供了比传统的对象(Object)更灵活、更高效的键值存储方式。
本文将深入研究Map
对象,包括其基本介绍、Map()
构造函数,以及一系列常用的实例方法,如clear
、delete
、get
、set
、has
、entries
、keys
、values
和forEach
。
一、Map基本介绍
1. 什么是Map对象?
Map
对象是ES6(ECMAScript 2015)引入的一种新的数据结构,用于存储键值对。与传统的对象不同,Map
的键可以是任何类型的值,包括对象、函数、基本类型等。
特点:
- 键的类型多样性:
Map
的键可以是任意类型,而对象的键只能是字符串或Symbol
。 - 键值对的插入顺序:
Map
对象会记录键值对的插入顺序,因此可以按顺序迭代。 - 迭代特性:
Map
内置了迭代器,可以直接使用for...of
循环遍历。
2. 为什么使用Map?
在某些情况下,我们需要使用复杂类型作为键来存储对应的值,传统的对象无法满足这个需求。Map
对象弥补了这一不足,为开发者提供了更灵活的数据存储方式。
优势:
- 键的灵活性:支持任意类型的键,包括对象和函数。
- 性能:在频繁增删键值对的情况下,
Map
可能比对象更高效。 - 内置方法:
Map
提供了丰富的操作方法,方便对集合进行管理。
二、Map()构造函数
1. 创建Map对象
Map
对象可以通过Map()
构造函数创建。可以创建一个空的Map
,也可以使用可迭代对象(如数组)初始化Map
。
语法:
// 创建空的Map
const map = new Map();
// 使用可迭代对象初始化Map
const map = new Map(iterable);
iterable
:一个可迭代的键值对数组,每个元素也是一个包含两个元素的数组,表示键和值。
2. 示例
(1)创建空的Map
const myMap = new Map();
(2)使用数组初始化Map
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
[42, 'value3']
]);
console.log(myMap.size); // 3
(3)使用对象作为键
const objKey = { id: 1 };
const myMap = new Map([
[objKey, 'Object Value']
]);
console.log(myMap.get(objKey)); // "Object Value"
3. 注意事项
- 如果
iterable
中存在重复的键,后面的值会覆盖前面的值。 - 键的比较采用“同值零等性”(
SameValueZero
),类似于严格相等===
,但NaN
被认为等于NaN
。
三、Map实例方法
Map
对象提供了一系列实例方法,用于操作键值对集合。以下是常用的实例方法:
clear()
delete(key)
get(key)
set(key, value)
has(key)
entries()
keys()
values()
forEach(callback[, thisArg])
1. clear()
定义
clear()
方法移除Map
对象中的所有键值对。
语法
map.clear();
示例
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
console.log(myMap.size); // 2
myMap.clear();
console.log(myMap.size); // 0
console.log(myMap.has('key1')); // false
注意事项
clear()
方法不返回值,执行后Map
对象将变为空。
2. delete(key)
定义
delete()
方法移除Map
对象中与指定键对应的键值对。
语法
map.delete(key);
key
:要移除的键。
返回值
true
:如果成功移除对应的键值对。false
:如果键不存在。
示例
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
console.log(myMap.size); // 2
myMap.delete('key1'); // 返回 true
console.log(myMap.size); // 1
console.log(myMap.has('key1')); // false
myMap.delete('key3'); // 返回 false,因为键不存在
注意事项
- 如果键不存在,
delete()
方法返回false
,Map
对象不发生变化。
3. get(key)
定义
get()
方法返回Map
对象中与指定键对应的值。如果不存在该键,返回undefined
。
语法
map.get(key);
key
:要获取其值的键。
示例
const myMap = new Map([
['key1', 'value1'],
[42, 'value2']
]);
console.log(myMap.get('key1')); // "value1"
console.log(myMap.get(42)); // "value2"
console.log(myMap.get('key3')); // undefined
注意事项
- 与
Object
的属性访问不同,Map
的键可以是任意类型,包括undefined
和null
。
4. set(key, value)
定义
set()
方法在Map
对象中添加或更新一个键值对。
语法
map.set(key, value);
key
:键。value
:值。
返回值
- 返回
Map
对象本身,可以链式调用。
示例
const myMap = new Map();
myMap.set('key1', 'value1');
myMap.set('key2', 'value2').set('key3', 'value3'); // 链式调用
console.log(myMap.size); // 3
console.log(myMap.get('key2')); // "value2"
// 更新已有键的值
myMap.set('key1', 'newValue1');
console.log(myMap.get('key1')); // "newValue1"
注意事项
- 如果键已存在,
set()
方法会更新其对应的值。 - 键的比较采用“同值零等性”。
5. has(key)
定义
has()
方法返回一个布尔值,表示Map
对象中是否存在指定的键。
语法
map.has(key);
key
:要检查的键。
示例
const myMap = new Map([
['key1', 'value1'],
[NaN, 'value2']
]);
console.log(myMap.has('key1')); // true
console.log(myMap.has('key2')); // false
console.log(myMap.has(NaN)); // true
注意事项
- 键的比较采用“同值零等性”,因此
NaN
被认为等于NaN
。
6. entries()
定义
entries()
方法返回一个新的Iterator
对象,包含Map
对象中所有的键值对,按插入顺序排列。每个元素是一个包含键和值的数组。
语法
map.entries();
示例
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
for (const [key, value] of myMap.entries()) {
console.log(`${key} => ${value}`);
}
// 输出:
// key1 => value1
// key2 => value2
// key3 => value3
注意事项
entries()
方法默认用于Map
对象的迭代,因此可以直接使用for...of
循环遍历Map
对象。
for (const [key, value] of myMap) {
console.log(`${key} => ${value}`);
}
7. keys()
定义
keys()
方法返回一个新的Iterator
对象,包含Map
对象中所有的键,按插入顺序排列。
语法
map.keys();
示例
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
for (const key of myMap.keys()) {
console.log(key);
}
// 输出:
// key1
// key2
// key3
8. values()
定义
values()
方法返回一个新的Iterator
对象,包含Map
对象中所有的值,按插入顺序排列。
语法
map.values();
示例
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
for (const value of myMap.values()) {
console.log(value);
}
// 输出:
// value1
// value2
// value3
9. forEach(callback[, thisArg])
定义
forEach()
方法对Map
对象中的每个键值对,按插入顺序调用一次callback
函数。
语法
map.forEach(callback[, thisArg]);
callback(value, key, map)
:为每个键值对执行的函数。value
:当前元素的值。key
:当前元素的键。map
:调用forEach()
的Map
对象本身。
thisArg
:可选,执行callback
函数时,用作this
的值。
示例
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
myMap.forEach((value, key, map) => {
console.log(`${key} => ${value}`);
});
// 输出:
// key1 => value1
// key2 => value2
// key3 => value3
使用thisArg
const myMap = new Map([
[1, 10],
[2, 20],
[3, 30]
]);
const multiplier = {
factor: 2,
multiply(value) {
return value * this.factor;
}
};
myMap.forEach(function(value, key) {
console.log(`${key} => ${this.multiply(value)}`);
}, multiplier);
// 输出:
// 1 => 20
// 2 => 40
// 3 => 60
注意事项
- 与数组的
forEach()
不同,Map
的forEach()
回调函数的第一个参数是value
,第二个参数是key
。 - 如果需要中途停止迭代,可以考虑使用
for...of
循环,配合break
或return
。
四、Map的遍历
除了上述的entries()
、keys()
、values()
和forEach()
方法,Map
对象还可以通过迭代器和解构赋值进行遍历。
1. 使用for...of
循环
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
for (const [key, value] of myMap) {
console.log(`${key} => ${value}`);
}
2. 使用扩展运算符
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
const mapArray = [...myMap];
console.log(mapArray); // [['key1', 'value1'], ['key2', 'value2']]
3. 使用Array.from()
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
const keysArray = Array.from(myMap.keys());
console.log(keysArray); // ['key1', 'key2']
const valuesArray = Array.from(myMap.values());
console.log(valuesArray); // ['value1', 'value2']
五、Map与Object的比较
1. 键的类型
- Map:键可以是任意类型,包括对象、函数、基本类型等。
- Object:键只能是字符串或
Symbol
类型,其他类型会被转换为字符串。
2. 键的顺序
- Map:按照插入顺序保持键的顺序。
- Object:键的顺序不保证,在不同的JavaScript引擎中可能不同。
3. 获取键值对的数量
- Map:使用
size
属性直接获取键值对数量。 - Object:需要手动计算,例如使用
Object.keys(obj).length
。
4. 性能
- Map:在频繁增删键值对的情况下,
Map
的性能可能更好。 - Object:在键值对较少的情况下,性能差异不明显。
5. 底层实现
- Map:专门用于映射键值对的数据结构。
- Object:JavaScript的基础类型,用于构建对象。
六、应用示例
1. 使用对象作为键
const user1 = { name: 'Alice' };
const user2 = { name: 'Bob' };
const userRoles = new Map();
userRoles.set(user1, 'Admin');
userRoles.set(user2, 'Editor');
console.log(userRoles.get(user1)); // "Admin"
console.log(userRoles.get(user2)); // "Editor"
2. 统计字符出现次数
function countChars(str) {
const charCount = new Map();
for (const char of str) {
const count = charCount.get(char) || 0;
charCount.set(char, count + 1);
}
return charCount;
}
const result = countChars('hello world');
for (const [char, count] of result) {
console.log(`'${char}': ${count}`);
}
// 输出:
// 'h': 1
// 'e': 1
// 'l': 3
// 'o': 2
// ' ': 1
// 'w': 1
// 'r': 1
// 'd': 1
3. 缓存函数结果(Memoization)
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// 示例函数:计算斐波那契数列
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(40)); // 快速计算出结果
七、注意事项
-
键的相等性:
Map
使用“同值零等性”比较键,这意味着0
和-0
被认为是相同的键,而NaN
可以作为有效的键,并且NaN === NaN
在Map
中为true
。const myMap = new Map(); myMap.set(NaN, 'value'); console.log(myMap.get(NaN)); // "value"
-
避免与
Object
混淆:Map
对象与普通对象在使用上有很多不同之处,特别是在键的类型和方法的使用上。务必根据实际需求选择合适的数据结构。 -
不可变性:
Map
对象本身是可变的,如果需要不可变的数据结构,可以考虑使用Immutable.js
等库。
参考资料
- MDN Web Docs - Map
- ECMAScript® 2015 Language Specification - Map Objects
- JavaScript高级程序设计(第4版)
- Understanding ECMAScript 6 - Map and Set