---
title: Map和Object          
date: 2022/4/26 10:00:00
tags:                    
- map
- object          
author: 张芳利         
---

# Map和Object
在 JavaScript 中，普通对象和 ES6 的新对象 Map 都可以存储键值对，平时普通对象用的较多，现在着重了解一下Map
<a name="AZnLW"></a>
## 描述
Map 对象存有键值对，其中的键可以是任何数据类型。<br />Map 对象记得键的原始插入顺序。<br />Map 对象具有表示映射大小的属性。
<a name="g2duN"></a>


<!-- more -->
## 创建
构造函数创建
```javascript
new Map([iterable])
```
Iterable 可以是一个数组或者其他 iterable 对象，其元素为键值对(两个元素的数组，例如: [[ 1, 'one' ],[ 2, 'two' ]])。 每个键值对都会添加到新的 Map。null 会被当做 undefined。
<a name="d1eEZ"></a>
## 方法和属性
```javascript
let map = new Map()
```
打印map可得<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22328960/1650869031897-18b102f5-2bf1-40b1-ae59-50055cfeae95.png#clientId=u6819a0e1-7327-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=343&id=ubcd9b3cb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=309&originWidth=481&originalType=binary&ratio=1&rotation=0&showTitle=false&size=21436&status=done&style=shadow&taskId=ubf631d28-9c30-4623-a110-07aaeb60424&title=&width=534.4444586023876)
```javascript
Map(0) {size: 0}
  [[Entries]]
  No properties
  size: 0
  [[Prototype]]: Map
  clear: ƒ clear()
  constructor: ƒ Map()
  delete: ƒ delete()
  entries: ƒ entries()
  forEach: ƒ forEach()
  get: ƒ ()
  has: ƒ has()
  keys: ƒ keys()
  set: ƒ ()
  size: 0
  values: ƒ values()
  Symbol(Symbol.iterator): ƒ entries()
  Symbol(Symbol.toStringTag): "Map"
  get size: ƒ size()
  [[Prototype]]: Object
```
<a name="HHGjW"></a>
### size
访问属性，用于返回 一个[Map](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map) 对象的成员数量。
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
console.log(map.size);//2 
```
<a name="gqCWw"></a>
### has()
返回一个bool值，用来表明map 中是否存在指定元素,返回布尔值
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])

console.log(map.has('name'));// true
console.log(map.has('hahahah')  //false
```
<a name="Jqcus"></a>
### set()
为 Map 对象添加或更新一个指定了键（key）和值（value）的（新）键值对
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');
console.log(map);  //Map(3) {'name' => 'wawa', 'age' => 18, 'bar' => 'foo'}
```
<a name="LXC1g"></a>
### get()
返回某个 Map 对象中的一个指定元素。
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');
console.log(map.get('name'));//wawa
```
<a name="EmioU"></a>
### keys()
返回一个引用的 **Iterator** 对象。它包含按照顺序插入 Map 对象中每个元素的key值
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');
let keys = 	map.keys()
console.log(keys); //MapIterator {'name', 'age', 'bar'}

for(let k of keys){
  console.log(k);   // name  age bar
}
```
<a name="iIwd6"></a>
### values()
返回一个新的Iterator对象。它包含按顺序插入Map对象中每个元素的value值。
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');
let values = 	map.values()
console.log(values); //MapIterator {'wawa', 18, 'foo'}

for(let k of values){
  console.log(k);   
}
```
<a name="DkShq"></a>
### entries()
返回一个新的包含 [key, value] 对的 **Iterator** 对象，返回的迭代器的迭代顺序与 Map 对象的插入顺序相同。
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');
let entries = map.entries()
console.log(entries); //MapIterator {'name' => 'wawa', 'age' => 18, 'bar' => 'foo'}
console.log(entries.next().value); //(2) ['name', 'wawa']
console.log(entries.next().value); //(2)(2) ['age', 18]
console.log(entries.next().value); //(2) (2) ['bar', 'foo'] 
```

- next()   是**Iterator** 对象的方法
<a name="C2l2a"></a>
### clear()
移除Map对象中的所有元素
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');

map.clear()
console.log(map.size);//0
```
<a name="bkyEw"></a>
### delete()
移除 Map 对象中指定的元素,返回布尔值
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');


console.log(map.delete('name'));//true
console.log(map.delete('namess'));//false
```
<a name="mTQEz"></a>
### forEach()
按照插入顺序依次对 Map 中每个键/值对执行一次给定的函数
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');

map.forEach((value,key,map)=>{
  console.log(`value:${value},key:${key}`);
})
//value:wawa,key:name  value:18,key:age value:foo,key:bar
```
<a name="rynQ5"></a>
### for...of... 
Map可以使用for..of循环来实现迭代
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');

for(let m of map){
  console.log(m);
}
//(2) ['name', 'wawa'] ['age', 18]  ['bar', 'foo']
```
<a name="Nfw1C"></a>
### 利用散布运算符...遍历集合
```javascript
let map = new Map([
  ['name','wawa'],
  ['age',18]
])
map.set('bar', 'foo');
let keys = 	map.keys()
console.log(keys); //MapIterator {'name', 'age', 'bar'}

console.log(...keys); // 'name', 'age', 'bar'
console.log(...map)  // (2) ['name', 'wawa']['age', 18] ['bar', 'foo']
```
<a name="KrSzR"></a>
## 和普通对象的区别
Objects 和 Maps 类似的是，它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。<br />不同的是：
<a name="McD5X"></a>
### 1. 默认值

   1. Map 默认情况不包含任何键。只包含显式插入的键。
   1. 一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
<a name="PK2H7"></a>
### 2. 键类型

   1. 一个Object 的键必须是一个 [String](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String) 或是[Symbol](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol)。
   1. 一个 Map的键可以是任意值，包括函数、对象或任意基本类型。
<a name="UavZb"></a>
### 3. 键顺序

   1. 一个 Object 的键是无序的
   1. Map 中的 key 是有序的。因此，当迭代的时候，一个 Map 对象以插入的顺序返回键值。
<a name="ei7Uf"></a>
### 4. Size

   1. Object 的键值对个数只能手动计算
   1.  Map 的键值对个数可以轻易地通过[size](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map/size) 属性获取
<a name="qowlQ"></a>
### 5. 迭代

   1. 迭代一个Object需要以某种方式获取它的键然后才能迭代
   1. 使用 for...of 语句或 Map.prototype.forEach 直接迭代 Map 的属性
<a name="lmBoK"></a>
### 序列化和解析

   1. 普通对象支持 JSON 序列化 
   1. Map 默认无法获取正确数据
<a name="ANmN0"></a>
### 性能

   1. 在频繁增删键值对的场景下表现更好
   1. 在频繁添加和删除键值对的场景下未作出优化

<a name="j3rp8"></a>
## 迭代协议：
迭代协议具体分为两个协议：可迭代协议和 迭代器协议。<br />**可迭代协议**：允许 JavaScript 对象定义或定制它们的迭代行为，例如，在一个 for..of 结构中，哪些值可以被遍历到。一些内置类型同时是内置可迭代对象，并且有默认的迭代行为，比如 Array 或者 Map，而其他内置类型则不是（比如 Object)）<br />要成为**可迭代**对象， 一个对象必须实现 **@@iterator** 方法。这意味着对象（或者它[原型链](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)上的某个对象）必须有一个键为 @@iterator 的属性，可通过常量 [Symbol.iterator](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator) 访问该属性：

| 属性 | 值 |
| --- | --- |
| [Symbol.iterator] | 一个无参数的函数，其返回值为一个符合[迭代器协议](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#%E8%BF%AD%E4%BB%A3%E5%99%A8%E5%8D%8F%E8%AE%AE)的对象。 |

**迭代器协议：**定义了产生一系列值（无论是有限个还是无限个）的标准方式。当值为有限个时，所有的值都被迭代完毕后，则会返回一个默认返回值<br />只有实现了一个拥有以下语义（semantic）的 **next()** 方法，一个对象才能成为迭代器：

| 属性 | 值 |
| --- | --- |
| **next** | 一个无参数或者一个参数的函数，返回一个应当拥有以下两个属性的对象：<br />**done（boolean）**<br />如果迭代器可以产生序列中的下一个值，则为 false。<br />如果迭代器已将序列迭代完毕，则为 true。<br />这种情况下，value 是可选的，如果它依然存在，即为迭代结束之后的默认返回值。<br />**next()** 方法必须返回一个对象，<br />该对象应当有两个属性： done 和 value，如果返回了一个非对象值（比如 false 或 undefined），<br />则会抛出一个 [TypeError](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError) 异常（"iterator.next() returned a non-object value"）。 |


Iterator 迭代器是一种接口，为不同的数据结构提供统一的访问机制，这个访问机制主要是遍历，我们知道，在数组、在对象、在类数组、在map、在set里面，都可以用for of或者扩展运算符来得到一个数组或者是遍历当前的数据结构，为什么能够遍历的原因就是因为存在这个Iterator 迭代器这个东西，所以我们可以用for of来提供一个统一的遍历，因此这个底层或者是Itrator它最初是为了for of而设计的。

为了给所有的[数据结构](https://so.csdn.net/so/search?q=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84&spm=1001.2101.3001.7020)有一个统一的遍历接口，统一的访问机制，因此就产生了迭代器。<br />迭代器的特点：

- 按某种次序排列数据结构
- 为for of提供一个遍历支持
- 为不同的数据结构提供统一的访问机制（目的）

目前所有的内置可迭代对象如下：[String](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String)、[Array](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array)、[TypedArray](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)、[Map](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map) 和 [Set](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set)等，它们的原型对象都实现了 @@iterator 方法。对象（Object）之所以没有默认部署Iterator接口，是因为对象的哪个属性先遍历，哪个属性后遍历是不确定的，需要开发者手动指定。<br />例如：由上面的学习，我们知道map的values() 返回的就是一个Iterator对象

```javascript
let map = new Map([
      ['name','zz'],
      ['age',15]
    ])
  console.log(map.values().next()); // {value: 'zz', done: false}
```
展开语法... :<br />其内部实现也使用了同样的迭代协议
```javascript
console.log(...'string') // s t r i n g
```
例如：[String](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String) 是一个内置的可迭代对象
```javascript
let str = 'string'
// 利用Symbol.iterator 属性获取一个迭代器对象
let it = str[Symbol.iterator]()
console.log(it); // StringIterator {}
console.log(it.next());  // {value: 's', done: false}
```

<a name="Wg0mh"></a>
### 问题：obj is not iterable
```javascript
let obj = {
    name:'张小张',
    age:18
  }
console.log([...obj]); // Uncaught TypeError: obj is not iterable
```
怎么做好利用展开值且不报错？？<br />根据[mdn](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#%E8%BF%AD%E4%BB%A3%E5%99%A8%E7%A4%BA%E4%BE%8B)迭代器的介绍：<br />给obj加上一个属性[Symbol.iterator]，且返回next()
```javascript
obj[Symbol.iterator] = function() {
    let nextIndex = 0;
    return {
        next: function () {
            let array = Object.values(obj)
            return nextIndex < array.length ? {
                value: array[nextIndex++],
                done: false
            } : {
                done: true
            };
        }
    };
}
			
console.log([...obj]) // ['张小张', 18]
```



