一、复杂的数据结构

  • 存储带键的数据(keyed)集合的对象
  • 存储有序集合的数组
  • 带键的数据项的集合:Map

    Map

    一、ECMAScript2015引入了一个新的数据结构来将一个值映射到另一个值。
    二、一个Map对象就是一个带键的数据项的集合,可以按照数据插入时的顺序遍历所有的元素。
    Map / 映射 - 图1

| 【示例】```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

  1. |
  2. | --- |
  3. <a name="ZBfHF"></a>
  4. ## 键
  5. 一、map[key]不是使用Map的正确方式<br />1、虽然map[key]也有效,例如我们可以设置map[key] = 2,这样会将map视为 JavaScript plain object,因此它暗含了所有相应的限制(没有对象键等)。<br />2、所以我们应该使用map方法:setget等。<br />二、Map 还可以使用对象作为键。
  6. | 【示例】```javascript
  7. let john = { name: "John" };
  8. // 存储每个用户的来访次数
  9. let visitsCountMap = new Map();
  10. // john 是 Map 中的键
  11. visitsCountMap.set(john, 123);
  12. 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())

![image.png](https://cdn.nlark.com/yuque/0/2022/png/355497/1652231369755-a632dc3f-484a-453f-b090-0d945a06da04.png#clientId=u0c4330f4-ceec-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=149&id=h27Sw&margin=%5Bobject%20Object%5D&name=image.png&originHeight=296&originWidth=792&originalType=binary&ratio=1&rotation=0&showTitle=false&size=99830&status=done&style=none&taskId=u52df2a90-2fdc-4bab-a3d3-82238912429&title=&width=400) |
| --- |

三、迭代的顺序与插入值的顺序相同。与普通的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相同。 | | —- |