在 JavaScript 中,普通对象和 ES6 的新对象 Map 都可以存储键值对,平时普通对象用的较多,现在着重了解一下Map
描述
Map 对象存有键值对,其中的键可以是任何数据类型。
Map 对象记得键的原始插入顺序。
Map 对象具有表示映射大小的属性。
创建
构造函数创建
new Map([iterable])
Iterable 可以是一个数组或者其他 iterable 对象,其元素为键值对(两个元素的数组,例如: [[ 1, ‘one’ ],[ 2, ‘two’ ]])。 每个键值对都会添加到新的 Map。null 会被当做 undefined。
方法和属性
let map = new Map()
打印map可得
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
size
访问属性,用于返回 一个Map 对象的成员数量。
let map = new Map([ ['name','wawa'], ['age',18] ]) console.log(map.size);//2
has()
返回一个bool值,用来表明map 中是否存在指定元素,返回布尔值
let map = new Map([ ['name','wawa'], ['age',18] ]) console.log(map.has('name'));// true console.log(map.has('hahahah') //false
set()
为 Map 对象添加或更新一个指定了键(key)和值(value)的(新)键值对
let map = new Map([ ['name','wawa'], ['age',18] ]) map.set('bar', 'foo'); console.log(map); //Map(3) {'name' => 'wawa', 'age' => 18, 'bar' => 'foo'}
get()
返回某个 Map 对象中的一个指定元素。
let map = new Map([ ['name','wawa'], ['age',18] ]) map.set('bar', 'foo'); console.log(map.get('name'));//wawa
keys()
返回一个引用的 Iterator 对象。它包含按照顺序插入 Map 对象中每个元素的key值
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 }
values()
返回一个新的Iterator对象。它包含按顺序插入Map对象中每个元素的value值。
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); }
entries()
返回一个新的包含 [key, value] 对的 Iterator 对象,返回的迭代器的迭代顺序与 Map 对象的插入顺序相同。
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 对象的方法
clear()
移除Map对象中的所有元素
javascriptlet map = new Map([ ['name','wawa'], ['age',18] ]) map.set('bar', 'foo'); map.clear() console.log(map.size);//0
delete()
移除 Map 对象中指定的元素,返回布尔值
javascriptlet map = new Map([ ['name','wawa'], ['age',18] ]) map.set('bar', 'foo'); console.log(map.delete('name'));//true console.log(map.delete('namess'));//false
forEach()
按照插入顺序依次对 Map 中每个键/值对执行一次给定的函数
javascriptlet 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
for…of…
Map可以使用for..of循环来实现迭代
javascriptlet 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']
利用散布运算符…遍历集合
javascriptlet 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']
和普通对象的区别
Objects 和 Maps 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。
不同的是:
1. 默认值
- Map 默认情况不包含任何键。只包含显式插入的键。
- 一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
2. 键类型
- 一个Object 的键必须是一个 String 或是Symbol。
- 一个 Map的键可以是任意值,包括函数、对象或任意基本类型。
3. 键顺序
- 一个 Object 的键是无序的
- Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。
4. Size
- Object 的键值对个数只能手动计算
- Map 的键值对个数可以轻易地通过size 属性获取
5. 迭代
- 迭代一个Object需要以某种方式获取它的键然后才能迭代
- 使用 for…of 语句或 Map.prototype.forEach 直接迭代 Map 的属性
序列化和解析
- 普通对象支持 JSON 序列化
- Map 默认无法获取正确数据
性能
- 在频繁增删键值对的场景下表现更好
- 在频繁添加和删除键值对的场景下未作出优化
迭代协议:
迭代协议具体分为两个协议:可迭代协议和 迭代器协议。
可迭代协议:允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object))
要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性:
属性 | 值 |
---|---|
[Symbol.iterator] | 一个无参数的函数,其返回值为一个符合迭代器协议的对象。 |
迭代器协议:定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值
只有实现了一个拥有以下语义(semantic)的 next() 方法,一个对象才能成为迭代器:
属性 | 值 |
---|---|
next | 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象: done(boolean) 如果迭代器可以产生序列中的下一个值,则为 false。 如果迭代器已将序列迭代完毕,则为 true。 这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。 next() 方法必须返回一个对象, 该对象应当有两个属性: done 和 value,如果返回了一个非对象值(比如 false 或 undefined), 则会抛出一个 TypeError 异常(”iterator.next() returned a non-object value”)。 |
Iterator 迭代器是一种接口,为不同的数据结构提供统一的访问机制,这个访问机制主要是遍历,我们知道,在数组、在对象、在类数组、在map、在set里面,都可以用for of或者扩展运算符来得到一个数组或者是遍历当前的数据结构,为什么能够遍历的原因就是因为存在这个Iterator 迭代器这个东西,所以我们可以用for of来提供一个统一的遍历,因此这个底层或者是Itrator它最初是为了for of而设计的。
为了给所有的数据结构有一个统一的遍历接口,统一的访问机制,因此就产生了迭代器。
迭代器的特点:
- 按某种次序排列数据结构
- 为for of提供一个遍历支持
- 为不同的数据结构提供统一的访问机制(目的)
目前所有的内置可迭代对象如下:String、Array、TypedArray、Map 和 Set等,它们的原型对象都实现了 @@iterator 方法。对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。
例如:由上面的学习,我们知道map的values() 返回的就是一个Iterator对象
let map = new Map([ ['name','zz'], ['age',15] ]) console.log(map.values().next()); // {value: 'zz', done: false}
展开语法… :
其内部实现也使用了同样的迭代协议
console.log(...'string') // s t r i n g
例如:String 是一个内置的可迭代对象
let str = 'string' // 利用Symbol.iterator 属性获取一个迭代器对象 let it = str[Symbol.iterator]() console.log(it); // StringIterator {} console.log(it.next()); // {value: 's', done: false}
问题:obj is not iterable
let obj = { name:'张小张', age:18 } console.log([...obj]); // Uncaught TypeError: obj is not iterable
怎么做好利用展开值且不报错??
根据mdn迭代器的介绍:
给obj加上一个属性[Symbol.iterator],且返回next()
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]