一、Map
本质上是键值对的集合(Hash结构),它的键和值可以是任何数据类型。
1.语法
new Map([iterable])
- 参数[iterable]可以是一个数组或其他可迭代对象,其元素或为键值对,或为两个元素的数组,每个键值对都会添加到新的Map,null会被当作undefined。
- 以插入顺序迭代Map对象时,例如for...of,每次迭代返回一个[key,value]数组。
- 基于"SameValueZero" 算法,
NaN
是与NaN
相同的,其他值是根据===运算符的结果判断是否相等。
let m = new Map()
m.set(-0, 111)
m.get(+0) // 111
m.set(true, 1)
m.set('true', 2)
m.get(true) // 1
m.get('true') // 2
m.set(undefined, 3)
m.set(null, 4)
m.get(undefined) // 3
m.set(NaN, 123)
m.get(NaN) // 123
Map的键是与内存地址绑定的,只要内存地址不一样,就视为两个键。
2.属性
const m = new Map
// Map(0) {size: 0}
- Map.prototype.set(key, value):设置key、value,返回该Map对象。
m.set('a', 1) // Map(1) {'a' => 1}
m.set(2, 'b') // Map(2) {'a' => 1, 2 => 'b'}
m.set(undefined, 'c') // Map(3) {'a' => 1, 2 => 'b', undefined => 'c'}
对于同一个键多次赋值,后面的值会覆盖前面的值
Map.prototype.get(key)
:返回键对应的值,如果不存在,则返回undefined。
m.get('a') // 1
m.get(2) // 'b'
m.get(undefined) // 'c'
Map.prototype.size
:返回Map对象的键值对的数量。
m.size // 3
Map.prototype.has(key)
:返回一个布尔值,表示Map实例是否包含键对应的值。
m.has('a') // true
Map.prototype.delete(key)
:移除与该键关联的值,并返回一个布尔值。如果有这个值会删除成功并返回true,没有返回false。
m.delete('a') // true
m // Map(2) {2 => 'b', undefined => 'c'}
Map.prototype.clear()
:移除Map内的所有元素。
3.遍历方法
遍历的顺序就是插入的顺序。
const m = new Map([
['a', 1],
['b', 2]
])
Map.prototype.keys()
:返回所有键名
m.keys() // 'a', 'b'
Map.prototype.values()
:返回所有键值
m.values() // 1, 2
Map.prototype.entries()
: 返回所有[key, value]成员
m.entries() // {'a' => 1, 'b' => 2 }
Map.prototype.forEach(callbackFn(value, key, map) {}, thisArg)
:遍历所有成员- 参数
- 第一个参数(callbackFn):用来执行每一个元素
- 第二个参数(thisArg):在执行第一个参数时,用来绑定的this
- 返回值 undefined
- callbackFn中的三个参数
- value:元素的值
- key:元素的键
- map:将要遍历的集合对象
- 参数
m.forEach(function(value, key, map) {
console.log(`k[${key}]:${value}`)
}, this)
// k[a]:1, k[b]:2
4.map与object的区别
map和object都允许按键存取一个值,都可以删除键,可以检测一个键是否绑定了值。 区别:
- 对象都有自己的原型prototype,不过从ES5开始可以使用object.create(null)来创建一个没有原型的对象,Map默认情况不包含任何键,只包含显示插入的键。
- 对象的键只能是字符串或者Symbols,Map的键可以是任意值,包含函数、对象或任意基本类型。
- 对象的键是无序的,Map的key是有序的,当迭代的时候,以插入的顺序返回key值。
- 通过size属性可以得到Map中键值对的个数,对象键值对的个数只能用其他方式。
- Map在频繁增删键值对的场景下性能更好。 Map的使用场景:
- 需要动态地查询key
- 值类型不统一,可以互换
- key不是字符串类型的
- 键值对经常增加/删除
- 有任意个且非常容易改变的键值对
- 可以遍历(可以使用forEach()方法迭代映射)
二、Set
类似于数组,但是成员的值都是唯一的,不允许重复。(所以可以用来做去重操作)
[...new Set(array)] // 去重后用扩展运算符(...)快速转为数组格式
1.语法
new Set([iterable])
- 参数[iterable]:如果传递一个可迭代对象,它的所有元素将被添加到新的 Set中。如果不指定此参数或其值为null,则新的 Set为空。
- set对象是值的集合,可以按照插入的顺序迭代它的元素。
- 基于Same-value-zero equality算法,NaN和undefined都可以被存储在set中,NaN之间被视为相同的值
2.属性
const s = new Set()
// Set(0) {size: 0}
Set.prototype.add(value)
:在Set对象尾部添加一个元素。返回该Set对象。
s.add(1).add(2).add(2)
// Set(2) {1, 2}
Set.prototype.size
:返回Set
实例的成员总数。
s.size // 2
Set.prototype.has(value)
:返回一个布尔值,表示该值是否为Set
的成员。Set.prototype.delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。Set.prototype.clear()
:清除所有成员,没有返回值。Set.prototype.constructor
:构造函数,默认就是Set
函数。
3.遍历方法
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器 keys、values、entries的使用与数组相同。set结构没有键名,只有键值,keys与values返回结果一致,entries返回一个类似map的结构,键名与键值相等。
const s = new Set()
s.add('a').add('b').add('c')
s.entries()
// ['a', 'a']
// ['b', 'b']
// ['c', 'c']
Set.prototype.forEach(callbackFn(value, key, map) {}, thisArg)
:使用回调函数遍历每个成员,与map使用相似,其中value与key输出相同。
s.forEach(function(value, key, map) {
console.log(`k[${key}]:${value}`)
}, this)
// k[a]:a
// k[b]:b
// k[c]:c
本来是打算再整理一下map与set的实现原理,后来没有看到源码,和原理相关的文章也非常少,等之后学习后再补充完整,大致是和java中hashMap实现原理相似,不过js没有线程的问题,主要是由链表、hash思想与桶来实现。
为什么要用链表呢?主要是为了解决哈希冲突
哈希冲突:当对某个元素进行哈希运算,得到一个存储地址,发现该地址已被其他元素占用时,就是哈希冲突。
既然map类似于object,set类似于array,那为什么要重新提出两个新的数据结构呢?因为如果要判断数组中是否存在某个元素时,现有的方法底层实现基本都是基于for循环遍历实现,时间复杂度为O(n),而Set底层数据结构是哈希表,数组在底层的数据结构是一个线性表,是一段连续的内存,根据索引查找元素时非常快,但是根据元素查索引就只能根据索引遍历出每一个数组元素,然后和要查找的数据元素进行对比,对于有序数组,可以使用二分查找、插值查找、斐波那契查找等,可将时间复杂度提为O(logn)。而哈希表中的元素位置是根据hash算法得出的,在要查找某个元素时,将该元素放入计算哈希值的算法中,得到哈希值,对哈希表长度求模,就可以得到元素在哈希表中的位置(不考虑哈希冲突的情况下,时间复杂度为O(1))。
等之后再开一个专栏来做leetcode刷题的记录,会写一篇用hash表解题的题目汇总。