一、复杂的数据结构
- 存储带键的数据(keyed)集合的对象
- 存储有序集合的数组
- 带键的数据项的集合:Map
Map
一、ECMAScript2015引入了一个新的数据结构来将一个值映射到另一个值。
二、一个Map对象就是一个带键的数据项的集合,可以按照数据插入时的顺序遍历所有的元素。
| 【示例】```javascript let map = new Map();
map.set(‘1’, ‘str1’); // 字符串键 map.set(1, ‘num1’); // 数字键 // 与对象不同,键不会被转换成字符串。键可以是任何类型。 map.set(true, ‘bool1’); // 布尔值键
// 还记得普通的 Object 吗? 它会将键转化为字符串 // Map 则会保留键的类型,所以下面这两个结果不同: alert( map.get(1) ); // ‘num1’ alert( map.get(‘1’) ); // ‘str1’
alert( map.size ); // 3
|
| --- |
<a name="ZBfHF"></a>
## 键
一、map[key]不是使用Map的正确方式<br />1、虽然map[key]也有效,例如我们可以设置map[key] = 2,这样会将map视为 JavaScript 的 plain object,因此它暗含了所有相应的限制(没有对象键等)。<br />2、所以我们应该使用map方法:set和get等。<br />二、Map 还可以使用对象作为键。
| 【示例】```javascript
let john = { name: "John" };
// 存储每个用户的来访次数
let visitsCountMap = new Map();
// john 是 Map 中的键
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
| | —- |
1、使用对象作为键是Map最值得注意和重要的功能之一。对于字符串键,Object(普通对象)也能正常使用,但对于对象键则不行。
| 【示例】```javascript let john = { name: “John” };
let visitsCountObj = {}; // 尝试使用对象
visitsCountObj[john] = 123; // 尝试将 john 对象作为键
// 是写成了这样! alert( visitsCountObj[“[object Object]”] ); // 123
|
| --- |
2、因为visitsCountObj是一个对象,它会将所有的键如john转换为字符串,所以我们得到字符串键"[object Object]"。这显然不是我们想要的结果。<br />三、Map是怎么比较键的?<br />1、Map使用[SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero)算法来比较键是否相等。它和严格等于===差不多,但区别是NaN被看成是等于NaN。所以NaN也可以被用作键。<br />2、这个算法不能被改变或者自定义。
<a name="ftbnR"></a>
## 链式调用
1、每一次map.set调用都会返回 map 本身,所以我们可以进行“链式”调用:
| 【示例】```javascript
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
| | —- |
Map 迭代
一、可以使用for…of 或 forEach(Map有内置的forEach方法)来遍历Map
| 【示例】```javascript var saying = new Map() sayings.set(‘dog’, ‘woof’) sayings.set(‘cat’, ‘meow’) sayings.set(‘elephant’, ‘toot’) console.log(sayings.size) // 3 sayings.get(‘fox’) // undefined sayings.has(‘bird’) // false sayings.delete(‘dog’) sayings.has(‘dog’) // false
for (var [key, value] of sayings) { console.log(key + ‘goes’ + value) } // ‘cat goes meow’ // ‘elephant goes toot’
sayings.clear() console.log(sayings.size) // 0
|
| --- |
二、如果要在map里使用循环,可以使用以下三个方法:
- map.keys()—— 遍历并返回所有的键(returns an iterable for keys),
- map.values()—— 遍历并返回所有的值(returns an iterable for values),
- map.entries()—— 遍历并返回所有的实体(returns an iterable for entries)[key, value],for..of在默认情况下使用的就是这个。
| 【示例】```javascript
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// 遍历所有的键(vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// 遍历所有的值(amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// 遍历所有的实体 [key, value]
for (let entry of recipeMap) { // 与 recipeMap.entries() 相同
alert(entry); // cucumber,500 (and so on)
}
// 对每个键值对 (key, value) 运行 forEach 函数
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
| | —- |
| 【示例】```javascript const map = new Map([ [2, ‘t1’], [3, ‘t2’], [4, ‘t3’] ]) console.log(‘map.keys():’, map.keys()) console.log(‘map.keys().next()’, map.keys().next())
 |
| --- |
三、迭代的顺序与插入值的顺序相同。与普通的Object不同,Map保留了此顺序。
<a name="hgqgq"></a>
# Object与Map
<a name="gSPsW"></a>
## Object和Map的比较
一、一般地,objects会被用于将字符串类型映射到数值。Object允许设置键值对、根据键获取值、删除键、检测某个键是否存在。<br />二、Object、Map的差别<br />1、最大的差别:Object的键均为Strings类型,在Map里键可以是任意类型。<br />2、必须手动计算Object的尺寸,但是可以很容易地获取使用Map的尺寸。<br />3、Map的遍历遵循元素的插入顺序。<br />4、Object有原型,所以映射中有一些缺省的键。(可以用map = Object.create(null)回避)<br />三、用Map还是用Object<br />1、如果键在运行时才知道,或者所有的键类型相同,所有的值类型相同,那就使用Map<br />2、如果需要将原始值存储为键,则用Map,因为Object将每个键视为字符串,不管它是一个值、布尔值还是任何其他原始值。<br />3、如果需要对个别元素进行操作,使用Object。
<a name="v8fJl"></a>
## Object.entries:从对象创建 Map
一、当创建一个Map后,我们可以传入一个带有键值对的数组(或其它可迭代对象)来进行初始化,
| 【示例】```javascript
// 键值对 [key, value] 数组
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
| | —- |
二、如果我们想从一个已有的普通对象(plain object)来创建一个Map,那么我们可以使用内建方法Object.entries(obj),该方法返回对象的键/值对数组,该数组格式完全按照Map所需的格式。
| 【示例1】从一个对象创建一个 Map:```javascript let obj = { name: “John”, age: 30 };
let map = new Map(Object.entries(obj)); // Object.entries返回键/值对数组:[ [“name”,”John”], [“age”, 30] ]。这就是Map所需要的格式
alert( map.get(‘name’) ); // John
|
| --- |
<a name="zJErM"></a>
## Object.fromEntries:从 Map 创建对象
一、Object.fromEntries方法的作用是相反的:给定一个具有[key, value]键值对的数组,它会根据给定数组创建一个对象:
| 【示例】```javascript
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
// 现在 prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
| | —- |
二、可以使用Object.fromEntries从Map得到一个普通对象(plain object)。
| 【示例】我们在Map中存储了一些数据,但是我们需要把这些数据传给需要普通对象(plain object)的第三方代码。```javascript let map = new Map(); map.set(‘banana’, 1); map.set(‘orange’, 2); map.set(‘meat’, 4);
let obj = Object.fromEntries(map.entries()); // 创建一个普通对象(plain object)(*)
// 完成了! // obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2
1、调用map.entries()将返回一个可迭代的键/值对,这刚好是Object.fromEntries所需要的格式。<br />2、我们可以把带(*)这一行写得更短:```javascript
let obj = Object.fromEntries(map); // 省掉 .entries()
(1)上面的代码作用也是一样的,因为Object.fromEntries期望得到一个可迭代对象作为参数,而不一定是数组。并且map的标准迭代会返回跟map.entries()一样的键/值对。因此,我们可以获得一个普通对象(plain object),其键/值对与map相同。 | | —- |