ES6中的Set和Map数据结构

1,749 阅读6分钟

一、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']

image.png

  • 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表解题的题目汇总。