JavaScript 中的 Map, Set, WeakMap, WeakSet
Posted GoldenaArcher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript 中的 Map, Set, WeakMap, WeakSet相关的知识,希望对你有一定的参考价值。
javascript 中的 Map, Set, WeakMap, WeakSet
之前还真不知道 WeakMap 和 WeakSet……
依旧来自「JavaScript 高级程序设计」的读书笔记。
Map
对比
先进行对比:
对于多数 Web 开发来说,使用 Object 还是 Map 只是个人偏好问题,影响不大。不过对于在乎性能和内存的用户来说,二者还是有区别的
Object | Map | |
---|---|---|
内存占用 | 浏览器实现不同 | 浏览器实现不同 \\newline 但是给定固定内存大小,Map 能比 Object 多存储 50%的键值对 |
插入性能 | 大致相当 | 大致相当 \\newline 一般来说性能会稍微好一些 |
插入顺序 | 不会维护 | 会维护 |
查找速度 | 大型数据源中,Object 和 Map 的性能差异极小 \\newline 但是数据量比较小的情况下,使用 Object 更快 \\newline 所以涉及大量查找的情况使用 Object 会比较好 | 大型数据源中,Object 和 Map 的性能差异极小 |
删除性能 | 使用 delete 删除对象的属性一直为人所诟病 \\newline 在 JavaScript 高级程序设计第四章学习笔记 中也提到过: \\newline 另外,使用 delete 操作也会动态更新属性,从而使得两个类无法共享同样的隐藏类 \\newline 很多时候都会使用将属性值设置为 null 或 undefined 作为折中 | 相比较而言,Map 的 delete 方法都比插入和查找更快 \\newline 如果代码涉及到大量删除的操作,那么毋庸置疑的应该使用 Map |
个人体感大概如下:
- 中台部分适合用 Map,毕竟大量涉及 增删改
- 渲染方面使用 Object 为佳,毕竟数据操作较少,渲染多以查找为主。
Map 的基本 API
-
创建实例
通过使用
new
关键字加上Map
构造函数去创建一个空的映射:const m = new Map();
想要在创建的时候同时初始化实例,可以给
Map
构造函数传入一个可迭代对象,需要包含键/值对数组。可迭代对象中的每个键/值对都会按照迭代顺序插入到新映射实例中:// 使用嵌套数组初始化映射 const m1 = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); alert(m1.size); // 3 // 使用自定义迭代器初始化映射 // 这个用的好少…… const m2 = new Map({ [Symbol.iterator]: function*() { yield ["key1", "val1"]; yield ["key2", "val2"]; yield ["key3", "val3"]; } }); alert(m2.size); // 3 // 映射期待的键/值对,无论是否提供 const m3 = new Map([[]]); alert(m3.has(undefined)); // true alert(m3.get(undefined)); // undefined
-
添加值
使用
set()
,另外,与与Object
只能使用数值、字符串或符号作为键不同,Map
可以使用任何 JavaScript 数据类型作为键。键的内部使用 SameValueZero 进行比较操作,相当于严格相等的标准。
let contacts = new Map(); // 第一个参数为键,第二个参数为值 contacts.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" });
-
删除值
delete()
删除一个值clear()
删除所有值
-
查询值
has()
确定有没有这个键值对get()
获取这个键值对
-
修改值
没找到有方法可以直接更新/修改值,但是根据日常开发走的都是 non-immutable,即不可变性来说,比较合适的方法应该是通过
get()
方法获取值,进行深拷贝,更新拷贝,再利用 Map 只允许存在一个键的特性使用set()
方法进行覆写。如果在乎顺序的话,那也只能用
get()
方法获取值,然后直接对引用进行更新了。
Map 的顺序与迭代
与 Object 类型的一个主要差异就是,Map 的实例会维护键值对的插入顺序,因此可以根据插入的顺序进行迭代。
-
如果使用迭代器的话,可以使用
entries()
const map = new Map([ [1, "a"], [2, "b"], [3, "c"], ]); for (let pair of map.entries()) { console.log(pair); } // (2) [1, "a"] // (2) [2, "b"] // (2) [3, "c"]
-
直接调用回调方式,则可以使用
forEach()
// map 的值一样 map.forEach((key, val) => console.log(key, val)); // a 1 // b 2 // c 3
-
如果只想要键或是值,可以用
keys
或者values
方法进行调用:// keys for (let key of map.keys()) { console.log(key); } // 1 // 2 // 3 // values for (let val of map.values()) { console.log(val); } // a // b // c
键和值在迭代器遍历时都是可以修改的,但是映射内部的引用无法修改。当然,这并不妨碍修改作为键或值对象内部的属性,因为这样并不影响他们在映射实例中的身份。
根据案例的理解是:
如果键是字符串,那么键无法被修改
如果键是对象,修改键不会改变通过 键 能够找到对应的 值,即二者之间的引用关系。
反之亦然,操作值也无法改变当前的值其键值对的引用关系。
WeakMap
特性:
- WeakMap 是 Map 的兄弟类型,其 API 也是 Map 的子集
- WeakMap 中的 weak 描述的是 JavaScript 垃圾回收程序对待弱映射中键的方式
WeakMap 的基本 API
基本可以参考一下 Map 的 API 使用,毕竟 WeakMap 的 API 是 Map 的子集。
有一点,弱映射的键只能是 Object,或继承自 Object 的类型,尝试使用非对象作为键会抛出 TypeError
;值的类型没有限制。
弱键
弱键的定义就在于,WeakMap 中的键不会阻碍垃圾回收,它只会随着别的引用的变化而变化。即,一旦 WeakMap 中的键,其引用被销毁了,那么对应的值也会被销毁。而只要 WeakMap 中的键的引用一直还在,那么对应的值就不会被销毁。
不可迭代键
鉴于 WeakMap 中的键在任何时候都可能会被销毁,因此没有提供可以迭代的函数,也因此没有提供可以一次性销毁所有键值对的 clear
的方法。
也因为无法迭代键值对,所以在不知道键的情况下就无法从 WeakMap 中获取到对应的值。
使用弱映射
提供的一些相关使用案例:
-
私有变量
因为只有在知道 键 才能获取 值 的特性,因此 WeakMap 可以 拟态 私有变量,以书中的案例来说:
const User = (() => { const wm = new WeakMap(); class User { constructor(id) { this.idProperty = Symbol("id"); this.setId(id); } setPrivate(property, value) { const privateMembers = wm.get(this) || {}; privateMembers[property] = value; wm.set(this, privateMembers); } getPrivate(property) { return wm.get(this)[property]; } setId(id) { this.setPrivate(this.idProperty, id); } getId(id) { return this.getPrivate(this.idProperty); } } return User; })(); const user = new User(123); alert(user.getId()); // 123 user.setId(456); alert(user.getId()); // 456
通过闭包,外部就无法获取
vm
的键值对,而内部类中,只有知道了vm
的键才能够获取对应的值。这种情况就可以拟态传统 OOP 语言的私有变量。 -
DOM 节点元数据
因为 WeakMap 不会妨碍垃圾回收,因此非常适合保存关联元数据。
const m = new Map(); const loginButton = document.querySelector("#login"); // Associates some metadata with the node m.set(loginButton, { disabled: true });
当页面中的
#login
被销毁了,那么 WeakMap 中对应的值也会被垃圾回收。
Set
Set 的很多 API 与行为与 Map 是共有的,因此操作上会有一些相似性。
Set 的基本 API
-
创建
创建一个空的集合:
const set = new Set();
创建含有值的集合:
const set1 = new Set(["val1", "val2", "val3", "val4"]);
-
增
使用
add
函数加值 -
删
使用
delete
删除值 -
改
与 Map 相似,因为一个 Set 中的值具有唯一性,所以可以利用覆写去修改值。
-
查
使用
has
可以判断 Set 中是否有对应值
Set 的顺序与迭代
Set 的顺序与迭代与 Map 相似,不过 entries
会有一点奇怪的特性:
const set = new Set([1, 2, 3]);
for (let pair of set.entries()) {
console.log(pair);
}
// (2)[(1, 1)];
// (2)[(2, 2)];
// (2)[(3, 3)];
如上面的例子所示,entries
会将同样的值输出两遍。
定义正式集合操作
这部分讲的是可以写一个子类继承 Set,然后在子类中实现静态方法,并返回想要取得的值:
class XSet extends Set {
union(...sets) {
return XSet.union(this, ...sets);
}
intersection(...sets) {
return XSet.intersection(this, ...sets);
}
// ... 省略其他函数若干
// 静态函数的实现
static union(a, ...bSets) {
const unionSet = new Set();
for (const b of bSets) {
for (const bValue of b) {
unionSet.add(bValue);
}
}
return unionSet;
}
// ... 省略其他函数
}
WeakSet
基本 API 与 Set 相似,基本特性与 WeakMap 相似。
使用案例也与 DOM 树相关,书中的案例是可以利用 WeakSet 保留操作过的 Set,例如说可以将禁用的节点存到 WeakSet 中,当节点在 DOM 树中被移除是,其在 WeakSet 的值也会被垃圾回收。
以上是关于JavaScript 中的 Map, Set, WeakMap, WeakSet的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript 中的 Map, Set, WeakMap, WeakSet