JavaScript -- Part2

数据结构

Symbol

ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

  1. Symbol 不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
  2. 每次调用 Symbol 都是创建了新的 Symbol
  3. 用于属性名,保证属性名的唯一性(例如, log 中 error / warning / info 不同类型的枚举对象)。常量使用 Symbol 值最大的好处,就是其他任何值都不可能有相同的值了。

Symbl 确保唯一,即使采用相同的名称,也会产生不同的值,我们创建一个字段,仅为知道对应 symbol 的人能访问,使用 symbol 很有用,symbol 并不是100%隐藏。

有内置方法 Object.getOwnPropertySymbols(obj)可以获得所有的symbol;也有一个方法 Reflect.ownKeys(obj)返回对象所有的键,包括symbol。

更多内容见 symbol

WeakMap 和 Map

在 JavaScript 中,WeakMapMap都是用于存储键值对的数据结构,但它们之间存在着多方面的区别,以下是详细介绍:

键的类型限制

  • Map
    Map可以使用任意类型的数据作为键,比如基本数据类型(数字、字符串、布尔值等)以及复杂数据类型(对象、数组等)都可以作为键来存储对应的键值对。例如:
1
2
3
4
5
6
7
const myMap = new Map();
const key1 = "key1";
const key2 = { name: "obj" };
const key3 = [1, 2];
myMap.set(key1, "value1");
myMap.set(key2, "value2");
myMap.set(key3, "value3");
  • WeakMap
    WeakMap的键只能是对象类型(包括普通对象、函数对象等),不能使用基本数据类型作为键。如果试图使用基本数据类型作为键去设置键值对,会抛出错误。例如:
1
2
3
4
const myWeakMap = new WeakMap();
const key1 = "key1"; // 基本数据类型,作为WeakMap的键会报错
const key2 = { name: "obj" };
myWeakMap.set(key2, "value2");

内存管理与垃圾回收机制

  • **Map**:
    Map对键值对的引用是强引用,这意味着只要 Map对象本身存在,它所存储的键值对就会一直保留在内存中,即使没有其他地方再引用这些键或值,也不会被垃圾回收机制自动回收,除非手动从 Map中删除相应的键值对(通过 delete方法等)。例如:
1
2
3
4
5
6
7
8
9
function createMap() {
const map = new Map();
const obj = { name: "test" };
map.set(obj, "value");
return map;
}

const myMap = createMap();
// 虽然createMap函数执行完了,obj在外部没有其他引用了,但它作为键在Map中,依然占用内存
  • WeakMap
    WeakMap对键的引用是弱引用,也就是说,当某个对象作为 WeakMap的键,且这个对象在外部没有其他强引用时(除了 WeakMap内部对它的弱引用),该对象及其对应的键值对会被垃圾回收机制自动回收,从而更有效地利用内存资源,避免内存泄漏等问题,尤其是在处理大量对象且希望自动释放不再使用的对象相关数据时非常有用。例如:
1
2
3
4
5
6
7
8
9
function createWeakMap() {
const weakMap = new WeakMap();
const obj = { name: "test" };
weakMap.set(obj, "value");
return weakMap;
}

const myWeakMap = createWeakMap();
// 当createWeakMap函数执行完,obj如果在外部没有其他强引用,它及其对应的键值对可能会被垃圾回收

可枚举性

  • Map
    Map是可枚举的,这意味着可以通过多种方式遍历它里面存储的所有键值对,比如使用 for...of循环结合 entries()方法来遍历获取每个键值对,或者使用 keys()方法遍历键、values()方法遍历值等。示例如下:

收起

js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const myMap = new Map();
myMap.set("key1", "value1");
myMap.set("key2", "value2");

for (const [key, value] of myMap.entries()) {
console.log(key, value);
}

for (const key of myMap.keys()) {
console.log(key);
}

for (const value of myMap.values()) {
console.log(value);
}
  • WeakMap
    WeakMap是不可枚举的,出于对其弱引用特性以及内存管理机制的考虑,防止在遍历过程中因对象被意外回收等情况导致的不一致或错误,所以不提供直接遍历所有键值对的方法,开发者无法像遍历 Map那样去遍历 WeakMap获取其内部存储的信息。

应用场景

  • **Map**:
    • 数据缓存:可以用于缓存一些计算结果,以输入数据(可以是各种类型,比如函数参数等)作为键,计算得到的结果作为值存储,下次遇到相同输入时直接从 Map中获取缓存的结果,提高计算效率。
    • 配置管理:存储应用的各种配置项,比如不同模块对应的配置对象等,以模块名称(字符串类型)等作为键,配置详情作为值,方便在程序运行过程中获取和修改配置信息。
  • **WeakMap**:
    • 关联对象与元数据:在对象的生命周期管理中,当希望给对象关联一些额外的元数据(比如对象是否被标记、对象的临时状态信息等),但又不想影响对象本身的垃圾回收机制时,可以使用 WeakMap,以对象作为键,元数据作为值,当对象不再被其他地方引用时,这些关联的元数据会自动随对象一起被回收。
    • DOM 节点与相关数据的关联:在前端开发中,对于一些 DOM 节点,可能想关联特定的数据(比如某个 DOM 元素对应的自定义事件处理数据等),使用 WeakMap能保证当 DOM 节点从页面移除,不再有外部强引用时,与之关联的数据也能被自动清理,避免内存占用和潜在的内存泄漏风险。

方法支持

  • **Map**:
    除了上述提到的 set(设置键值对)、get(获取对应键的值)、delete(删除指定键值对)、entries(返回可遍历的键值对迭代器)、keys(返回键的迭代器)、values(返回值的迭代器)方法外,还有 has(检查是否存在指定的键)、clear(清空所有键值对)等方法,功能较为丰富,便于对存储的数据进行全面的操作和管理。
  • **WeakMap**:
    只支持 setgethasdelete这几个基本的方法,用于简单的键值对操作,因为其设计重点在于弱引用和内存管理,并不需要像 Map那样复杂的遍历等功能相关的方法。

总之,WeakMapMap各有特点,在不同的应用场景下可以根据内存管理需求、键的类型以及是否需要遍历等因素来选择使用哪种数据结构,以便更好地处理数据存储和操作相关的问题。

注意事项

  1. 内存回收的不确定性:虽然 WeakMap的设计初衷是基于弱引用实现自动的内存回收,但垃圾回收机制的具体执行时间是由 JavaScript 引擎决定的,具有一定的不确定性,所以不能精确控制对象及其对应键值对在何时被回收,在一些对内存释放时间要求很精准的场景下可能需要额外考虑其他策略或者结合其他技术手段来协同处理。
  2. 不适合频繁遍历的场景(针对 WeakMap):由于 WeakMap不可枚举,若业务场景需要频繁遍历数据结构来获取所有存储的信息,那么 WeakMap显然不适合,应选择 Map或者其他可遍历的数据结构来满足需求,避免因使用不恰当的数据结构导致功能无法实现或者代码逻辑复杂度过高。
  3. 键类型的错误使用(针对 WeakMap):在使用 WeakMap时,要特别留意键的类型必须是对象,开发过程中容易出现因疏忽使用了基本数据类型作为键而导致代码报错的情况,在代码审查或者编写代码时要仔细确认键的类型是否符合要求,确保正确使用 WeakMap的数据结构特性。


JavaScript -- Part2
http://example.com/2023/08/12/JavaScript-Part2/
作者
lyric
发布于
2023年8月12日
许可协议