JavaScript 系列 - Set 和 WeakSet

87 阅读4分钟

Set

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

特点

  • 因为 Set 中的值总是唯一的,所以需要判断两个值是否相等。

    内存地址不一样视为不同 value

    • +0 -0 相同
    • NaN 相同
    • 对象不等
  • 用作值的对象和其他集合类型在自己的内容或属性被修改时也不会改变

  • Set 实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作

场景

  • 去除数组重复

    function dedupe(array) {
      return Array.from(new Set(array));
    }
    function dedupe(array) {
      return [...new Set(array)];
    }
    
  • 去除字符串里面的重复字符

    function dedupeString(string) {
      return [...new Set(string)].join("");
    }
    

使用

构造函数

new Set([iterable])

  • 传递一个可迭代对象,它的所有元素将不重复地被添加到新的 Set 中。
  • 如果不指定此参数或其值为 null,则新的 Set 为空

静态属性

Set[Symbol.species] 访问器属性返回Set的构造函数。

class MySet extends Set {
  // Overwrite MySet species to the parent Set constructor
  static get [Symbol.species]() {
    return Set;
  }
}

实例属性

Set.prototype.size

返回 Set 对象中(唯一的)元素的个数

实例方法

  • Set.prototype.add(value)

    • 如果 Set 对象中没有具有相同值的元素,则 add()  方法将插入一个具有指定值的新元素到 Set 对象中
    • 链式调用
  • Set.prototype.delete(value)

    从 Set 对象中删除指定的值(如果该值在 Set 中)

  • Set.prototype.has(value)

    返回一个布尔值来指示对应的值是否存在于 Set 对象中。

  • Set.prototype.clear()

    移除 Set 对象中所有元素

  • Set.prototype[@@iterator]()

    const set1 = new Set();
    
    set1.add(42);
    set1.add('forty two');
    
    const iterator1 = set1[Symbol.iterator]();
    
    console.log(iterator1.next().value);
    // Expected output: 42
    
    console.log(iterator1.next().value);
    // Expected output: "forty two"
    
  • 遍历

    • Set.prototype.keys()

      • keys() 方法是 values() 方法的别名
      • 一个新的迭代器对象,按插入顺序包含给定 Set 中每个元素的值
    • Set.prototype.values()

      返回一个新的迭代器对象,该对象按插入顺序包含 Set 对象中每个元素的值。

    • Set.prototype.entries()

      返回一个新的迭代器对象,这个对象包含的元素是类似 [value, value] 形式的数组

    • Set.prototype.forEach()

      Set 对象中的每个值按插入顺序执行一次提供的函数

转换

  • Array.from(set)
  • [...set]

借用数组方法

  • set = new Set([...set].map()
  • set = new Set([...set].filter()

集合运算

  • 子集

    function isSuperset(set, subset) {
      for (let elem of subset) {
        if (!set.has(elem)) {
          return false;
        }
      }
      return true;
    }
    
  • 并集 new Set([...a, ...b])

    function union(setA, setB) {
      let _union = new Set(setA);
      for (let elem of setB) {
        _union.add(elem);
      }
      return _union;
    }
    
  • 交集 new Set([...a].filter((x) => b.has(x)))

    function intersection(setA, setB) {
      let _intersection = new Set();
      for (let elem of setB) {
        if (setA.has(elem)) {
          _intersection.add(elem);
        }
      }
      return _intersection;
    }
    
  • 差集 new Set([...a].filter((x) => !b.has(x)))

    function difference(setA, setB) {
      let _difference = new Set(setA);
      for (let elem of setB) {
        _difference.delete(elem);
      }
      return _difference;
    }
    
    function symmetricDifference(setA, setB) {
      let _difference = new Set(setA);
      for (let elem of setB) {
        if (_difference.has(elem)) {
          _difference.delete(elem);
        } else {
          _difference.add(elem);
        }
      }
      return _difference;
    }
    
class XSet extends Set {
  // 并集
  union(...sets) {
    return XSet.union(this, ...sets);
  }
  // 交集
  intersection(...sets) {
    return XSet.intersection(this, ...sets);
  }
  // 差分
  difference(set) {
    return XSet.difference(this, set);
  }
  // 对等差分
  symmertricDifference(set) {
    return XSet.symmertricDifference(this, set);
  }
  // 笛卡尔积
  cartesianProduct(set) {
    return XSet.cartesianProduct(this, set);
  }
  // 幂集
  powerSet() {
    return XSet.powerSet(this);
  }
  static union(a, ...bSets) {
    const unionSet = new XSet(a);
    for (const b of bSets) {
      for (const bValue of b) {
        unionSet.add(bValue);
      }
    }
    return unionSet;
  }
  static intersection(a, ...bSets) {
    const intersectionSet = new XSet(a);
    for (const aValue of intersectionSet) {
      for (const b of bSets) {
        if (!b.has(aValue)) {
          intersectionSet.delete(aValue);
        }
      }
    }
    return intersectionSet;
  }
  static difference(a, b) {
    const differenceSet = new XSet(a);
    for (const bValue of b) {
      if (a.has(bValue)) {
        differenceSet.delete(bValue);
      }
    }
    return differenceSet;
  }
  static symmertricDifference(a, b) {
    return a.union(b).difference(a.intersection(b));
  }
  static cartesianProduct(a, b) {
    const cartesianProductSet = new XSet();
    for (const aValue of a) {
      for (const bValue of b) {
        cartesianProductSet.add([aValue, bValue]);
      }
    }
    return cartesianProductSet;
  }
  static powerSet(a) {
    const powerSet = new XSet().add(new XSet());
    for (const aValue of a) {
      for (const set of new XSet(powerSet)) {
        powerSet.add(new XSet(set).add(aValue));
      }
    }
    return powerSet;
  }
}
var a = new XSet(["a", "b"]);
var b = new XSet(["a"]);
var union = a.union(b);
var intersection = a.intersection(b);
var difference = a.difference(b);
var symmertricDifference = a.symmertricDifference(b);
var cartesianProduct = a.cartesianProduct(b);
var powerSet = a.powerSet(b);
console.log(
  union,
  intersection,
  difference,
  symmertricDifference,
  cartesianProduct,
  powerSet
);

ArraySet 的对比

  • 数组中用于判断元素是否存在的 indexOf 函数效率低下
  • Set 对象允许根据值删除元素,而数组中必须使用基于下标的 splice 方法
  • 数组的 indexOf 方法无法找到 NaN
  • Set 对象存储不重复的值,所以不需要手动处理包含重复值的情况

WeakSet

  • 成员只能是对象和 Symbol 值,而不能是其他类型的值。
  • 如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存
  • new WeakSet([iterable])
  • 不可枚举

方法

  • WeakSet.prototype.add(value)
  • WeakSet.prototype.delete(value)
  • WeakSet.prototype.has(value)

用途

  • 储存 DOM 节点

  • 实例方法只能在实例上调用

    const foos = new WeakSet()
    class Foo {
      constructor() {
        foos.add(this)
      }
      method () {
        if (!foos.has(this)) {
          throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
        }
      }
    }
    
  • 集合相关运算

  • 循环引用

    // 对 传入的 subject 对象 内部存储的所有内容执行回调
    function execRecursively(fn, subject, _refs = new WeakSet()) {
      // 避免无限递归
      if (_refs.has(subject)) {
        return;
      }
    
      fn(subject);
      if (typeof subject === "object") {
        _refs.add(subject);
        for (const key in subject) {
          execRecursively(fn, subject[key], _refs);
        }
      }
    }
    
    const foo = {
      foo: "Foo",
      bar: {
        bar: "Bar",
      },
    };
    
    foo.bar.baz = foo; // 循环引用!
    execRecursively((obj) => console.log(obj), foo);