轻松掌握ES6的Set

基本用法

Set.prototype.add(value),在 Set 对象尾部添加一个元素

1
2
3
const set = new Set();
set.add(1);
console.log(set); // Set(1) {1}

需要注意的是

1
2
3
const set = new Set([1, 2, 3, 4]);
set.add([5, 6]);
console.log(set); // Set(5) {1,2,3,4,Array(2)}

数组作为构造参数传递进去时会被 set 解构,相当于把数组中的每一项 add 到 set 中,但使用 add 方法向 set 中添加数组时,整个数组是作为一个元素

Set.prototype.clear(),清除 Set 中的所有元素 Set.prototype.has(value),判断 value 是否存在于 Set 中 Set.prototype.delete(value) 删除 Set 中的某个值

1
2
3
4
5
6
const set = new Set([1, 2, 3, 4]);
set.has(1); // true
set.delete(2);
console.log(set); // Set(3) {1,3,4}
set.clear();
set.has(1); // false

遍历 Set 对象中的元素:forEach()、keys()、values()、entries()

forEach:

1
2
3
4
5
6
new Set([1, "foo", NaN]).forEach((value, key) => {
  console.log(`[${key}] = ${value}`);
});
// [1] = 1
// [foo] = foo
// [NaN] = NaN

Set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的

也可使用for...of

keys():

1
2
3
4
const set = new Set([1, "foo", NaN]).keys();
console.log(set.next().value); // 1
console.log(set.next().value); // "foo"
console.log(set.next().value); // NaN

values()、entries()跟 keys()大同小异,在此不作赘述

它的具体作用

  • 去重

    1
    2
    const array = [...new Set([1, 1, "1", 2, 3, 2, 4, NaN, NaN])];
    console.log(array); // [1, "1", 2, 3, 4, NaN]
    

    Set 不会将元素进行类型转换,所以 1 和”1”是不相等的,并且 Set 中 NaN 是等于 NaN 的

    降维去重并升序

    1
    2
    var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
    Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})
    
  • 速度快?
    首先我们创建一个数组和一个 Set

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let arr = [],
      set = new Set(),
      n = 1000000;
    console.time("Array");
    for (let i = 0; i < n; i++) {
      arr.push(i);
    }
    console.timeEnd("Array");
    console.time("Set");
    for (let i = 0; i < n; i++) {
      set.add(i);
    }
    console.timeEnd("Set");
    // Array: 29.285888671875ms
    // Set: 136.115966796875ms
    

    好的,刚开始就啪啪打脸,接下来测试查找元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let result;
    console.time("Array");
    result = arr.indexOf(123123) !== -1;
    console.timeEnd("Array");
    console.time("Set");
    result = set.has(123123);
    console.timeEnd("Set");
    // Array: 0.174072265625ms
    // Set: 0.003662109375ms
    

    查找元素相差几乎 50 倍

    我们的结论是:Set 的 add()比 Array 的 push()要慢,但是查找元素 has()远远比 indexOf()快,各位看官看看就行。

WeakSet

它和Set对象的区别有两点:

  • WeakSet 对象中只能存放对象引用, 不能存放值, 而 Set 对象都可以.
  • WeakSet 对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样, WeakSet 对象是无法被枚举的, 没有办法拿到它包含的所有元素. 在我刚看到弱引用时,我的想法是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var ws = new WeakSet();
var a = { foo: "bar" };
ws.add(a);
console.log(ws);
/* 应输出:
WeakSet 
__proto__: WeakSet
[[Entries]]: Array(1)
	0: value: {foo: "bar"}
  length: 1
*/

delete a;
console.log(ws);
/* 应输出:
WeakSet {}
__proto__: WeakSet
[[Entries]]: Array(0)
length: 0
*/

然而实际输出: :scream::scream::scream::fearful::flushed:,到底哪里有问题呢?
我们打印一下a对象console.log(a),却发现输出{foo: "bar" } 查找资料后发现:

在 Javascript 是可以使用 delete 来手动删除变量,通过这样的方法让 GC 来回收内存,但在 JS 中并不是所有的对象都可以被删除的, kangex 在他的博文中对此作了详细说明: Understanding delete 在 JS 中通过 var\function 声明因含有 DontDelete,而不可被删除; 但是对象的属性、数组成员却是可以删除的;

因此如果我们要回收某个对象可以使用 Object 来封装一下。

1
2
3
4
5
6
7
8
9
10
11
12
var test = {
  name: "test",
  content: {
    name: "content",
    will: "be clean"
  }
};
var ws = new WeakSet();
ws.add(test.content);
console.log("清理前", ws);
delete test.content;
console.log("清理后", ws);

然而 可以发现,我们已经成功删除了test中的content,然而还是没有起作用,说好的弱引用呢??:sob:
原来,JavaScript 语言中,内存的回收并不是在执行 delete 操作符断开引用后即时触发的,而是根据运行环境的不同、在不同的运行环境下根据不同浏览器的回收机制而异的。比如在 Chrome 中,我们可以在控制台里点击 CollectGarbage 按钮来进行内存回收(不起作用?),关于在不同浏览器环境下手动进行内存回收的具体异同,可参考:如何手动触发 JavaScript 垃圾回收行为?(此链接过老,大部分方法已失效)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var test = {
  name: "test",
  content: {
    name: "content",
    will: "be clean"
  }
};
var ws = new WeakSet();
ws.add(test.content);
console.log("清理前", ws); // 清理前 WeakSet 
test.content = null;
console.log("清理后", ws); // 清理后 WeakSet 

// -- 进行手动回收 --

console.log(ws); // WeakSet {}