site infoHacknerd | Tech Blog
blog cover

🎡 [ECMAScript6] 14.Set和Map

ES6JavaScript

Set

类似于数组,但成员的值都是一样的,没有重复值 。

javascriptCopy
const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4

add 不会添加重复值。可以用来去除数组或者字符重复值。

javascriptCopy
// 去除数组的重复成员
[...new Set(array)]

[...new Set('ababbc')].join('')
// "abc"

向 Set 加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身。

javascriptCopy
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}

Set实例属性和方法

  • Set.prototype.constructor: 构造函数
  • Set.prototype.size : 返回成员总数
  • 操作方法

  • Set.prototype.add
  • Set.prototype.set
  • Set.prototype.has
  • Set.prototype.clear
  • 遍历方法

  • Set.prototype.keys
  • Set.prototype.values
  • Set.prototype.entries
  • 1.keys values entries 方法返回的都是遍历器对象。由于Set没有键名,所以keys和values方法行为完全一致
  • javascriptCopy
    let set = new Set(['red', 'green', 'blue']);
    
    for (let item of set.keys()) {
      console.log(item);
    }
    // red
    // green
    // blue
    
    for (let item of set.values()) {
      console.log(item);
    }
    // red
    // green
    // blue
    
    for (let item of set.entries()) {
      console.log(item);
    }
    // ["red", "red"]
    // ["green", "green"]
    // ["blue", "blue"]

    Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。

    这意味着,可以省略values方法,直接用for...of循环遍历 Set。

    javascriptCopy
    let set = new Set(['red', 'green', 'blue']);
    
    for (let x of set) {
      console.log(x);
    }

    WeakSet

    与Map类似,但成员只能是Symbol或对象

    WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

    javascriptCopy
    const a = [[1, 2], [3, 4]]; // 数组成员只能是对象
    const ws = new WeakSet(a); // WeakSet [[1, 2], [3,4]]

    实例方法

    没有size属性不能被遍历

  • WeakSet.prototype.add
  • WeakSet.prototype.set
  • WeakSet.prototype.delete
  • Map

    JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

    比如:

    javascriptCopy
    const data = {};
    const element = document.getElementById('myDiv');
    
    data[element] = 'metadata';
    data['[object HTMLDivElement]'] // "metadata"

    上面代码原意是将一个 DOM 节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动转为字符串[object HTMLDivElement]。

    为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

    实例方法

  • Map.prototype.size
  • Map.prototype.set
  • Map.prototype.get
  • Map.prototype.has
  • Map.prototype.delete
  • Map.prototype.clear
  • 遍历方法

  • Map.prototype.keys
  • Map.prototype.entries
  • Map.prototype.values
  • Map.prototype.forEach
  • 可以用扩展运算符… , Map本身没有fliter map方法。但可以通过转换成数组实现

    WeakMap

    WeakMap与Map的区别有两点。

    首先,WeakMap只接受对象(null除外)和 Symbol 值作为键名,不接受其他类型的值作为键名。

    其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。

    WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。请看下面的例子。

    javascriptCopy
    const e1 = document.getElementById('foo');
    const e2 = document.getElementById('bar');
    const arr = [
      [e1, 'foo 元素'],
      [e2, 'bar 元素'],
    ];

    上面代码中,e1和e2是两个对象,我们通过arr数组对这两个对象添加一些文字说明。这就形成了arr对e1和e2的引用。

    一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放e1和e2占用的内存。

    javascriptCopy
    // 不需要 e1 和 e2 的时候
    // 必须手动删除引用
    arr [0] = null;
    arr [1] = null;

    基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。

    javascriptCopy
    const wm = new WeakMap();
    
    const element = document.getElementById('example');
    
    wm.set(element, 'some information');
    wm.get(element) // "some information"

    WeakRef

    ES2021 新增对象 用于创建对象弱引用

    实例方法

  • WeakRef.protoptype.deref 判断原始对象是否已被清除。
  • 弱引用对象的一大用处,就是作为缓存,未被清除时可以从缓存取值,一旦清除缓存就自动失效。

    javascriptCopy
    function makeWeakCached(f) {
      const cache = new Map();
      return key => {
        const ref = cache.get(key);
        if (ref) {
          const cached = ref.deref();
          if (cached !== undefined) return cached;
        }
    
        const fresh = f(key);
        cache.set(key, new WeakRef(fresh));
        return fresh;
      };
    }
    
    const getImageCached = makeWeakCached(getImage);

    FinalizationRegistry

    ES2021 引入了清理器注册表功能 FinalizationRegistry,用来指定目标对象被垃圾回收机制清除以后,所要执行的回调函数。

    javascriptCopy
    const registry = new FinalizationRegistry(heldValue => {
      // ....
    });
    
    registry.register(theObject, "some value"); // 注册所要观察的目标对象

    注册表不对目标对象theObject构成强引用,属于弱引用。因为强引用的话,原始对象就不会被垃圾回收机制清除,这就失去使用注册表的意义了。

    实例方法

  • FinalizationRegistry.prototype.register // 注册
  • FinalizationRegistry.prototype.unregister // 取消注册
  • javascriptCopy
    registry.register(theObject, "some value", theObject); // 第三个参数作为取消标记
    
    registry.unregister(theObject);

    下面使用FinalizationRegistry,对前一节的缓存函数进行增强。

    javascriptCopy
    function makeWeakCached(f) {
      const cache = new Map();
      const cleanup = new FinalizationRegistry(key => {
        const ref = cache.get(key);
        if (ref && !ref.deref()) cache.delete(key);
      });
    
      return key => {
        const ref = cache.get(key);
        if (ref) {
          const cached = ref.deref();
          if (cached !== undefined) return cached;
        }
    
        const fresh = f(key);
        cache.set(key, new WeakRef(fresh));
        cleanup.register(fresh, key);
        return fresh;
      };
    }
    
    const getImageCached = makeWeakCached(getImage);

    增加一个清理器注册表,一旦缓存的原始对象被垃圾回收机制清除,会自动执行一个回调函数。该回调函数会清除缓存里面已经失效的键。

    Contents

    • Set
    • WeakSet
    • Map
    • WeakMap
    • WeakRef
    • FinalizationRegistry

    2024/03/20 02:08