大家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
今天将重点对比两个数据结构,即 Array 和 Set,话不多说,直接开始。
1.什么是 Set 和 Array?
到目前为止,每个使用 JS 的人都熟悉 Array,但数组到底是什么?一般来说,数组是一种结构,表示在连续内存中分配的数据块(数字、对象等)。比如:
[1, 2, 3, 2];
那 Set 又是什么?Set(集合) 作为数学概念更为人熟知,它是一种抽象数据类型,它只包含不同的元素/对象,不需要按索引顺序分配。比如:
{
1, 2, 3;
}
所以从概念上看,Array 和 Set 在技术上是不同的概念。您可能会注意到,这里最大的区别之一是 Array 中的元素可以重复(除非您告诉它不要重复),而在 Set 中,它们不能重复(无论您如何决定)。
此外,Array 被认为是“索引集合(indexed collection)”类型的数据结构,而 Set 被认为是“键控集合(keyed collection)”。
- 索引集合:是按索引值排序的数据集合
- 键控集合:是使用键的集合,其包含可按插入顺序迭代的元素
在编程世界中,存储相同的数据集(无重复)可以使用 Array 或 Set 作为结构来存储。 然而,选择正确的结构有助于提供最佳解决方案,这也是我们希望实现的目标。
2.如何构造 Set 和 Array?
2.1 构造 Array
数组非常简单,要在 JS 中声明新数组,可以采用如下方式:
var arr = []; //Empty array
var arr = [1, 2, 3]; //Array which contains 1,2,3
或者使用内置构造函数:
var arr = new Array(); //empty array
var arr = new Array(1, 2, 3); //Array which contains 1,2,3
或者使用 Array.from 方法:
var arr = Array.from('123'); //["1","2","3"]
注意:除非你真的需要,否则不要使用 new Array(),因为:
- 它比普通的 [] 符号执行的慢得多
- [] 节省更多的打字时间
- 您可能最终会犯一些意想不到的错误,例如:
var arr1 = new Array(10);
//arr1[0] = undefined but arr1.length = 10
var arr2 = [10];
// arr2[0] = 10 and arr2.length = 1;
var arr3 = new Array(1, 2, 3);
//[1,2,3]
var arr4 = [1, 2, 3];
//[1,2,3]
2.2 构造 Set
只能通过 Set 内置的构造函数来创建 Set。
var emptySet = new Set();
var exampleSet = new Set([1, 2, 3]);
但是绝对不要这样:
new Set(1);
Set 接收可迭代对象作为其输入参数,并将分别创建 set 对象。因此,可以从一个数组构造一个集合,但是它只会包含来自该数组的不同元素,也就是没有重复元素。当然,也可以使用 Array.from() 方法将集合转换回数组。
var set = new Set([1, 2, 3]);
// {1,2,3}
var arr = Array.from(set);
//[1,2,3]
好的,既然知道如何创建它们,那么它们的功能呢?让我们对 Array/Set 提供的最基本的方法做一个小的比较。
2.2.1 定位/访问一个元素
- 首先,Set 不支持像 Array 那样通过索引随机访问元素,这意味着:
console.log(set[0]);
//undefined
console.log(arr[0]);
//1
- 由于 Array 数据存储在连续的内存中,CPU 能够通过预取而更快地访问数据。 因此,与其他类型的抽象数据类型相比,通常访问数组中的元素(例如在 for 循环中)会更快、更高效。
- 使用 Set.prototype.has(value) VS Array.prototype.indexOf(value) 检查元素是否在 Set 中的语法比 Array 更简单
console.log(set.has(0)); // boolean - false
console.log(arr.indexOf(0)); // -1
console.log(set.has(1)); //true
console.log(arr.indexOf(1)); //0
注意:ES6 确实提供了 Array.prototype.includes() ,它的行为类似于 has(),但是,它没有得到广泛支持,比如 IE 浏览器就是特例。
2.2.2 插入元素
通过使用 Array.prototype.push() 可以在时间复杂度为 O(1)的情况下快速完成向 Array 添加新元素,此时元素将被添加到数组的末尾。
arr.push(4); //[1,2,3,4]
或者也可以使用 Array.prototype.unshift() 在时间复杂度为 O(n)的情况下完成将元素添加到数组的开头,此时 n 是当前数组的长度。
arr.unshift(3); //[3,1,2,3]
arr.unshift(5, 6); //[5,6,3,1,2,3]
- 在 Set 中,只有一种方法可以添加新元素,即 Set.prototype.add()。 由于 Set 必须维护其集合成员之间的“不同”属性,因此在每次调用 add() 时,Set 都需要检查所有成员以确保没有重复。 通常 add() 将花费 O(n) 的运行时间。 然而,由于哈希表实现方法,Set 中的 add() 可能只需要 O(1)。
set.add(3); //{1,2,3}
set.add(4); //{1,2,3,4}
因此 Set 在添加元素方面几乎和 Array 时间复杂度一致。
2.2.3 移除元素
Array 如此流行的好处之一是因为它提供了许多不同的方法来删除元素,例如:
- Pop() : 删除并返回最后一个元素,时间复杂度 O(1)
arr.pop(); //return 4, [5,6,1,2,3]
- Shift() — 删除并返回第一个元素,时间复杂度 O(n)
arr.shift(); //return 5; [6,1,2,3]
- Splice(index, deleteCount): 从索引开始删除 deleteCount 个元素,时间复杂度最多为 O(n)。
arr.splice(0, 1); //[1,2,3]
在 Set 中可以使用如下方法来移除元素。
- Delete(element) — 从 Set 中删除特定的给定元素
set.delete(4); //{1,2,3}
- Clear() — 从 Set 中删除所有元素
set.clear(); //{}
虽然 Array 不支持本地构建的方法来删除特定的元素(除非知道它的索引),但需要一个额外的外部函数的帮助来查找该元素的索引并执行 splice(),而 Set 会更加简单。
此外,与目前仅具有上述最基本功能的 Set 相比,Array 确实提供了更多的原生功能(reduce()、reverse()、sort() 等)。
3.什么时候使用 Array?什么时候 Set?
- 首先,Set 不同于 Array。它并不是要完全取代 Array,而是提供额外的支持类型来完成 Array 缺失的部分。
- 由于 Set 只包含不同的元素,如果事先知道数据不会重复,使用 Set 会更加省心
- Set 的基本操作,如 union()、intersect()、difference() 等可以根据本机内置(built-in)操作轻松有效地实现。 由于 delete() 方法存在,使得两个 Set 的交并操作比数组容易的多
- Array 适用于保持元素有序以便快速访问,或进行大量修改(删除和添加元素)或任何需要对元素进行直接索引访问的操作(例如,尝试对 Set 而不是 Array 做二分查找,如何获取中间元素)
总的来说,Set 与 Array 相比并没有明显的优势,除非在特定情况下。例如当想要以最小成本维护数据的唯一性,或者同时处理大量不同的数据集时使用最基本的集合操作,无需直接访问元素。否则,Array 应该始终是首选。下一篇文章将重点介绍通过Set来提升程序性能的方案,欢迎大家持续关注~
参考资料
原文作者:Maya Shavin
原文链接:https://medium.com/front-end-weekly/es6-set-vs-array-what-and-when-efc055655e1a