Set
Set 这个数据结构类似数组,但是里面的元素值都是 唯一,不会有重复的值,无论此值是原始类型或引用类型 (object references)。在 JavaScript 当中,Set 本身是一种构造函数,用来生成 Set 这种数据结构,具体的做法是通过 new Set() 来生成实例。
Set 常见操作方法有
add(value):新增值至 Set 中delete(value):删除 Set 中的特定值has(value):检查 Set 中是否存在特定值size:获取 Set 中元素的数量
Set 中没有键值 (Key),因此使用 entries() 遍历时,返回的元素将是 [value, value] 的形式:
const set1 = new Set();
set1.add(42);
set1.add("forty two");
const iterator1 = set1.entries();
for (const entry of iterator1) {
console.log(entry);
// 预期输出:[42, 42]
// 预期输出:["forty two", "forty two"]
}
Set 和 WeakSet 的区别
WeakSet 的方法和使用部分与 Set 数据结构相近,本区块会专注在这两者不同之处
- WeakSet 内的元素值只允许是 对象 (Object),但 Set 可接受各种数据类型的值
const wSet = new WeakSet();
const a = [1, 2, 3];
const b = { name: "explainthis" };
wSet.add(a); // WeakSet {Array(3)}
wSet.add(b); // WeakSet {{...}}
wSet.add(1); // Uncaught TypeError: Invalid value used in weak set
- WeakSet 内的元素都是 “弱引用”(weak reference),可以被垃圾回收机制回收。假如使用 Set,即使某个被存入的值,在其他地方已经没有被引用,该值仍会存在于 Set 当中,不会被垃圾回收。但如果是 WeakSet,则会被垃圾回收。如果要更有意识地做内存管理,WeakSet 在许多时候能派上用场。
const disableElements = new WeakSet();
const loginButton = document.querySelector("#login");
disableElements.add(loginButton);
disableElements.has(loginButton); // true
Map
类似于 Object 的数据结构,都是用键与值 (key-value pair) 的形式储存数据格式,但还是有许多差异。Map 本身是一种构造函数,用来生成 Map 这种数据结构,具体做法是 new Map() 来生成实例。
Map 常见操作方法包括:
set(key, value):新增元素至 Map 中get(key):通过键 (Key) 查询特定元素并返回has(key):检查 Map 中是否存在特定键 (Key)delete(key):透从 Map 中删除特定元素size:获取 Map 中元素的数量
Map 常见遍历方法 (遍历顺序与元素放入 Map 的顺序相同):
values():返回 Map 中所有元素的值keys():返回 Map 中所有元素的键entries():返回 Map 中所有的元素,返回的会是[key, value]的形式
Map 和 WeakMap 的区别
WeakMap 的方法和使用部分与 Map 数据结构相近,但有以下区别:
-
WeakMap 中的键名 (Key) 只能是 对象 (Object) 和 Symbol,不接受其他数据类型作为键名,例如原始值 (primitive values) 如字符串、数字、布尔值等,但不包括 null。相比之下,Map 可以接受各种数据类型作为键名 (Key)。
-
WeakMap 中的键名是“弱引用”(weak reference),键名 (key) 所指向的对象可以被垃圾回收,此时的键名 (key) 是无效的
// 如果放入的对象在外面没有其他引用,在 WeakMap 中会被垃圾回收掉
let food = new WeakMap();
let fruit = { name: "apple" };
food.set(fruit, "good"); // 将 fruit 对象置入 WeakMap 中
fruit = null; // 移除 fruit 的引用
console.log(food);
// 因为 JavaScript 的垃圾回收时机会因为不同引擎而有差异,所以可能不会马上被回收,以上可能 log 出两种场景
// WeakMap {Object => "good"},fruit 的引用被移除,但对象可能还未被垃圾回收
// WeakMap(0) fruit 已经被垃圾回收,因此 WeakMap 中没有项目
// 一般的 Map,即使放入的对象在外面没有其他引用,仍在 Map 当中存放
let food = new Map();
let fruit = { name: "apple" };
food.set(fruit, "good");
console.log(food); // Map(1)
fruit = null;
console.log(food); // Map(1) fruit 不会被垃圾回收
在上方的强引用代码中,虽然 fruit 对象最后被重新赋值为 null (意思等同于无法再通过 fruit 变量获取该对象值,因为其中的引用被断开),但由于 food 与此对象间存在强引用,所以被保留在内存中,这就是前面提到的, 强引用会防止对象被垃圾回收,并将对象保留在内存当中; 弱引用则相反,并 不能防止对象被垃圾回 收,当 JavaScript 执行环境执行垃圾回收时,上述弱引用例子中的 fruit 对象会被从内存和 WeakMap 中删除。
弱引用的适用场景在于,如果引用的对象在未来可能会被删除的情况、且不想防止被垃圾回收时,就适合用 WeakMap 或 WeakSet。例如,如果我们想要记录一些与 DOM 节点相关的数据,有一种方法是使用 Expando 扩充节点上的信息,但坏处是会直接修改到这个 DOM 节点、且如果未来这个节点被移除时,相关信息不会被垃圾回收掉,这时如果是使用 WeakMap 就会是很好的替代方案。
备注:如果直接将弱引用代码的例子在 JavaScript 执行环境中执行,可能还是会看到 WeakMap 中有值,这是因为 JavaScript 执行环境会在特定的时间点执行垃圾回收。