Map、Set与Symbol
Map、Set与Symbol
JunsMap、Set 与 Symbol
前言
这几个数据结构是 ES6 新增的,还记得当时第一次面试被问到“你对这几个了解多少?有使用过吗?”时,啥也不会特尴尬 🫢,所以现在详细了解并整理一下这几个数据结构。
Map
Map 是什么?让我们来看一下 MDN 文档的介绍:
Map
对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为键或值。
可以看到其中几个关键点:保存键值对、记住键的原始插入顺序、任何值都能作为键或值
这些就是他的特点,相较于 object 不同的地方,不过其实他和 object 也有很多共同之处,而且很多时候用哪个都可以。
基本使用
// 创建一个空映射 |
初始化之后可以通过set()
来添加键值对,此外还可以通过get()
和has()
来查询,可以用size
属性来获取数量,还可以用delete()
和clear()
来删除值
const m = new Map() |
由于set()
方法返回的是映射实例,所以也可以初始化的时候连起来操作
const m = new Map().set('key1', 'val1') |
键的唯一性
在Map
中,一个键只能出现一次,其中键的比较基于SameValueZero (零值相等)算法,基本上是相当于使用严格对象相等的标准来检测。
SameValueZero 是 ES 规范内部定义,语言中不能使用,JS 提供的判断只有
==
、===
、Object.is()
。
const m = new Map() |
与严格相等一样,在映射中用作键和值的对象,在自己的内容或属性被修改是仍然保持不变
const m = new Map() |
不过这个相等算法,也可能会导致意想不到的冲突:
const m = new Map() |
顺序与迭代
Map
与Object
的一个主要差异就是,Map
会维护插入顺序,因此可以根据插入顺序来执行迭代操作。它提供了一个迭代器,可以通过entries()
或者Symbol.iterator
属性来获取
const m = new Map([ |
因为entries()
是默认迭代器,所以可以直接使用拓展操作把映射转成数组
const m = new Map([ |
也可以使用映射的forEach
来迭代
const m = new Map([ |
通过keys()
可以获取映射的键迭代器,values()
可以获得值的迭代器。
对比 Object
他们俩比较相似,而且用法也差不多,日常开发中用哪个基本相差不大,不过还是有一些优劣的。
下面的对比摘录自《JavaScript 高级程序设计(第 4 版)》
- 内存占用:Map 占用更少
- 插入性能:Map 性能更佳
- 查找速度:Object 更好
- 删除性能:Map 更好
关键词:迭代器、SameValueZero 算法、性能、Object
WeakMap
看名字就知道他和Map
关系很大,看看 MDN 介绍先:
WeakMap
是一种键值对的集合,其中的键必须是对象或非全局注册的符号,且值可以是任意的 JavaScript 类型,并且不会创建对它的键的强引用。换句话说,一个对象作为WeakMap
的键存在,不会阻止该对象被垃圾回收。一旦一个对象作为键被回收,那么在WeakMap
中相应的值便成为了进行垃圾回收的候选对象,只要它们没有其他的引用存在。唯一可以作为WeakMap
的键的类型是非全局注册的符号,因为非全局注册的符号是保证唯一的,并且不能被重新创建。
简单看就是,基本功能和Map
一样,但是他的键必须是对象,原始数据类型作为键会报错
const sy = Symbol() |
弱键
WeakMap
中的weak
表是键是弱弱的拿着,意思就是这些键不是正式引用,不会阻止垃圾回收。
看一个例子:
const wm = new WeakMap() |
set()
初始化了一个新的空对象并作为一个字符串的键。因为没有指向这个对象的其他引用,所以这行代码执行之后,这个对象键会被当作垃圾回收掉。然后这个键值对就从弱映射中消失了,变成了一个空映射。
另一个例子:
const wm = new WeakMap() |
在这个例子里面,container
维护了一个对弱映射键的引用,因此这个对象键不会成为垃圾回收的目标,但是当container.key = null
调用时,会清理掉。
同时也因为弱引用,所以他不可迭代,同时也没有clear()
方法
用处
因为他不会妨碍垃圾回收,所以说他非常适合保存关联元数据
const m = new Map() |
假设这个代码执行后,那个按钮从 DOM 树中删除掉了。但是 Map 还保存着引用,所以对应的 DOM 节点还会逗留在内存中。
如果是使用的弱映射,当节点被删除后,垃圾回收程序就可以立即释放内存。
const wm = new WeakMap() |
关键词:不可迭代、垃圾回收
Set
我一直理解的 set 就挺像数组的,但是里面都是唯一值,看看 MDN 咋说:
Set
对象允许你存储任何类型(无论是原始值还是对象引用)的唯一值。
Set
对象是值的合集(collection)。集合(set)中的元素只会出现一次,即集合中的元素是唯一的。你可以按照插入顺序迭代集合中的元素。插入顺序对应于add()
方法成功将每一个元素插入到集合中(即,调用add()
方法时集合中不存在相同的元素)的顺序。
他的 api 也和 Map 比较类似,而且他的值相等算法也是SameValueZero(零值相等)算法
基本使用
const s = new Set(['val1', 'val2']) |
初始化后可以使用add()
添加值,使用has()
查询,使用size
获取数量,以及可以用delete()
和clear()
删除元素。
顺序和迭代
与Map
类似,Set
也提供了迭代器,可以通过values()
或keys()
来获取,他俩是一样的,Symbol.iterator
属性引用的是values()
const s = new Set([1, '2']) |
如果是用entries()
或者forEach
则是俩个重复的值:
const s = new Set([1, '2']) |
用处
一般用的多的就是数组去重吧
const unique = arr => [...new Set(arr)] |
WeakSet
WeakSet
是可被垃圾回收的值的集合,包括对象和非全局注册的符号。WeakSet
中的值只能出现一次。它在WeakSet
的集合中是唯一的。它和
Set
对象的主要区别有:
WeakSet
只能是对象和符号的集合,它不能像Set
那样包含任何类型的任意值。WeakSet
持弱引用:WeakSet
中对象的引用为弱引用。如果没有其他的对WeakSet
中对象的引用存在,那么这些对象会被垃圾回收。
WeakSet
就和WeakMap
类似
作用
递归调用自身的函数需要一种通过跟踪哪些对象已被处理,来应对循环数据结构的方法。
// 对传入的 subject 对象内部存储的所有内容执行回调 |
Symbol
Symbol 是一种基本数据类型,每一个从Symbol()
获取的 symbol 值都是唯一的,一个 symbol 值可以作为对象属性的标识符,这是该数据类型仅有的目的。
symbol 是一种基本数据类型(primitive data type)。
Symbol()
函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:”new Symbol()
“。
Symbol('11') === Symbol('11') // false |
他不会被常规的枚举方法包含,如for...in
、Object.keys()
const age = Symbol('age') |