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 只是个人偏好问题,影响不大。不过对于在乎性能和内存的用户来说,二者还是有区别的

ObjectMap
内存占用浏览器实现不同浏览器实现不同 \\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 的实例会维护键值对的插入顺序,因此可以根据插入的顺序进行迭代。

  1. 如果使用迭代器的话,可以使用 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"]
    
  2. 直接调用回调方式,则可以使用 forEach()

    // map 的值一样
    map.forEach((key, val) => console.log(key, val));
    // a 1
    // b 2
    // c 3
    
  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 中获取到对应的值

使用弱映射

提供的一些相关使用案例:

  1. 私有变量

    因为只有在知道 键 才能获取 值 的特性,因此 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 语言的私有变量。

  2. 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中的set和map

JavaScript 中的 Map, Set, WeakMap, WeakSet

JavaScript中的Map和Set

如何在 Javascript 中的 Map 键上使用 .map()

javascript中的Set和Map数据结构

JavaScript 是不是对 Map 和 Set 使用哈希表?