《ES6修炼记——Set》

303 阅读6分钟

  在ES5时代,我们用于表示“集合”的数据结构主要是数组(Array)和对象(Object)。ES6标准引入了两个新的数据结构Set和Map,它们也表示“集合”。现在,我们拥有了四种常用的“集合”数据结构。并且,我们可以组合使用它们,来定义更加丰富的数据。
  本文将主要介绍Set数据结构的基本用法,并与数组进行对比,希望能帮助大家了解并合理使用Set。

基础篇

一、什么是Set?

  Set是一个构造函数,用于生成ES6中新增的数据结构——Set数据结构。这种数据结构与数组类似,但其成员的值都是唯一的,不能重复。

二、如何生成Set数据结构?

  和数组一样,Set也是一个对象。我们可以用Set构造函数来生成它。在浏览器控制台中打印的Set结构如下:

  Set函数也可以接收一个具有iterator接口的数据作为参数。原生的具有iterator接口的数据类型有:Array、Map、Set、String、TypedArray、函数的arguments对象、NodeList对象。所以,以上的数据结构都可以作为参数,来生成一个Set数据,例如:   

    const a = new Set([1,2,3])  // Set(3) {1, 2, 3}
    const b = new Set(new Map([['juejin','666']]))  // Set(1) {Array(2)}
    const c = new Set(new Set(['java','C','C++']))  // Set(3) {"java", "C", "C++"}
    const d = new Set('456')  // Set(3) {"4", "5", "6"}
    function test(name){
        console.log(new Set(arguments))
    }
    test('juejin')  // Set(1) {"juejin"}
    const e = new Set(document.querySelectorAll('div'))  // Set(141) {div#custom-bg, ...}

  需要注意以下两点:
  1、Set结构的成员都是独一无二的,当我们试图添加重复的值时,Set会自动帮我们过滤重复的值。利用这个特性,我们可以非常方便的实现数组去重和数学中的并集、交集、差集

    let a = new Set([1,2,2])  // Set(2) {1, 2}
    let b = new Set()
    b.add(1).add(2).add(2)  // Set(2) {1, 2}
    
    <!--去重:--> 
    let arr = [1,2,3,3]
    [...new Set(arr)]  // [1,2,3],任何iterator接口的对象,都可以用扩展运算符转为数组
    Array.from(new Set(arr))  // [1,2,3],Array.from可将部署了iterator接口的数据转为数组
    
    <!--并集、交集、差集-->
    let set1 = new Set([1,2,3])
    let set2 = new Set([4,3,2])
    
    let union = new Set([...set1,...set2])  // Set(4) {1,2,3,4}
    let intersect = new Set([...set1].filter(x=>set2.has(x))) // Set(2) {2,3}
    let difference = new Set([...set1].filter(x=>!set2.has(x)))  // Set(1) {1}

  2、Set内部判断两个值是否相等的算法类似于精确相等运算符(===)。区别在于,Set认为两个NaN是相等的,但是精确相等运算符会判断两者不相等。   

    let a = new Set()
    new Set().add(NaN).add(NaN)  // Set(1) {NaN}
    new Set().add(1).add('1')  // Set(2) {1, "1"}
    new Set().add({}).add({})  // Set(2) {{…}, {…}}

三、Set的属性和方法

属性:

  • Set.prototype.constructor:构造函数,默认Set函数
  • Set.prototype.size:返回Set成员总数

4个操作数据的方法:

方法 用途 返回值
add(value) 添加某个值 Set本身(可链式操作)
delete(value) 删除某个值 布尔值,是否删除成功
has(value) 判断参数是否为Set成员 布尔值,是否是Set成员
clear( ) 清除所有成员
    let set = new Set()
    set.add(1).add(2)
    set.size  // 2
    set.has(1)  // true
    set.delete(2)  //true
    set.has(2)  //false
    set.clear()

4个遍历方法:

方法 用途 返回值
keys(value) 返回键名的遍历器 键名的遍历器
values(value) 返回键值的遍历器 键值的遍历器
entries(value) 返回键值对的遍历器 键值对的遍历器
forEach( ) 使用回调函数遍历每个成员

注意:因为Set结构本身就带有iterator接口,所以也可以直接用for...of遍历。

    let set = new Set([1,2,3])
    set.keys()  // SetIterator {1, 2, 3}
    for(let item of set.keys()){
        console.log(item)
    }
    // 1
    // 2
    // 3
    
    set.values()  // SetIterator {1, 2, 3}
    for(let item of set.values()){
        console.log(item)
    }
    // 1
    // 2
    // 3
    
    set.entries()  // SetIterator {1 => 1, 2 => 2, 3 => 3},返回的遍历器同时包含键名和键值。
    for(let item of set.entries()){
        console.log(item)
    }
    // 因遍历器同事包含键名键值,所以每次打印出一个数组
    // [1,1]
    // [2,2]
    // [3,3]
    
    set.forEach(item => console.log(value*2))
    // 2
    // 4
    // 6
    for(let item of set){
        console.log(item)
    }
    // 1
    // 2
    // 3

深入篇

  通过上面的介绍我们可以发现,Set虽然有去重特性,但在api数量上,远不如数组丰富。例如,若想在遍历中同步修改原来的Set结构,Set没有直接的方法。我们只能先转成数组,处理数据后再新生成一个Set。这就意味着Set很难应付工作中复杂的业务场景。那么,Set与数组相比难道只有去重这个优势吗?接下来,我将从api和代码性能两个维度分析他们。

一、api

1、增

方法 分析
Set add add方法只能从末尾加入元素
Array push、unshift、splice 数组可以在任何位置加入一个元素

2、删

方法 分析
Set delete delete只能删除某个具体的值,Set没有删除某个位置的值的方法
Array pop、shift、splice 数组中可以删除任意位置的元素,但若想删除某个特定的值,则要先找到其位置后删除

3、改

方法 分析
Set Set不能修改某个元素
Array arr[0]=1

4、查

方法 分析
Set has 如果含有某个值,返回true,不会返回这个值
Array indexOf、find、findIndex、includes 数组的方法更加丰富,用法也更多样化

二、性能

  关于Set的运行速度,掘金上已经有了一篇很好的文章 《如何使用 Set 来提高代码的性能》,我就不再阐述。文章中两者运行时间的差距可能有些夸张,我用自己和朋友的电脑测量了几次,都没有达到作者所说的那个倍数,不过这些都不重要了。总的来说,在数据量大的时候,Set执行添加、删除、查找操作的速度要明显优于数组,尤其是删除操作,快了很多。

总结

  通过以上对Set的分析,我总结下个人对于Set的看法:
  1、Set像是一个仓库管理员。它从不关心物品的位置,当物品来了,它就直接扔进去;当某个东西不需要了,它就直接销毁;当你想知道仓库里有没有某个物品,它就立马告诉你答案。即使仓库里存储了大量的货物,它的速度依然很快,当然前提是不能重复!
  2、数组像是一个一丝不苟的超市管理员,他能记住超市里每一个物品的位置,他可以对超市里每一个商品进行各种骚操作。但是,当货物越来越多时,他也力不从心,速度越来越慢。
  3、如果你需要一个存储数据的地方,而数据的位置和顺序对你来说不重要,你也不会对数据进行其他处理,那么Set将是一个很好的选择。