这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
Set
Set对象可以存储任意类型的数据,且只能有唯一值。
原始数据类型进行值比较,对象则进行引用比较。
实例属性
size只读属性,返回元素个数
方法
add(value)在末尾添加元素clear()移除所有元素delete(value)删除与这个值相等的元素entries()返回一个包含所有元素且以插入顺序排列的迭代器,迭代器的每个值为[value, value](为了与Map保持一致)forEach(callbackFn, thisArg)以插入顺序,每一个元素作为参数调用callback方法has(value)返回是否包含这个值keys()和values()返回一个包含所有元素且以插入顺序排列的迭代器,迭代器的每个值为value
Set vs Array性能对比
插入操作
在插入个数10以下,两者的差别几乎没有,即使使用performance.now()去截取运行时长,也都是为0。
提高到了1万个,差距开始有一丝分别,Set开始比Array慢50%%,但实际的时长也只是0.1ms的区别,还是很细微的。
到了10万个,时长差距持续拉大到3倍。
当我们以1千万的时候,Set已经达到Array的10倍耗时。
var s0 = new Set()
var now = Date.now()
for(let i = 0; i<10000000; i++) {
s0.add(i)
}
console.log(Date.now() - now);
平均用时2289.8ms
var a0 = []
var now = Date.now()
for(let i = 0; i<100000000; i++) {
a0.push(i)
}
console.log(Date.now() - now);
平均用时194.2ms
当我想要再次提高,把次数提高到1亿的时候,Set直接报错。
Uncaught RangeError: Value undefined out of range for undefined options property undefined
at Set.add (<anonymous>)
at <anonymous>:4:8
原因分析
因为Array的push方法,只需要把元素添加到数组的末尾;而Set需要遍历整个集合的值并进行比对,如果没有发现相同的值,才添加到末尾。所以造成Set的集合元素越多,耗时越长。
查询操作
Set在查询的时候都是极快,无论查询的值的位置在哪里,都是几乎相同的耗时。相反,Array的查询耗时则与值所处在的位置有极大的相关性。
var s0 = new Set()
for(let i = 0; i<10000000; i++) {
s0.add(i)
}
var now = performance.now()
for(let i = 100; i<10000; i++) {
s0.has(i)
}
console.log(performance.now() - now);
var now = performance.now()
for(let i = 100000; i<110000; i++) {
s0.has(i)
}
console.log(performance.now() - now);
var now = performance.now()
for(let i = 5000000; i<5010000; i++) {
s0.has(i)
}
console.log(performance.now() - now);
平均用时2.5ms 2.8ms 2.1ms
var a0 = []
for(let i = 0; i<100000000; i++) {
a0.push(i)
}
var now = performance.now()
for(let i = 100; i<10000; i++) {
a0.includes(i)
}
console.log(performance.now() - now);
var now = performance.now()
for(let i = 100000; i<110000; i++) {
a0.includes(i)
}
console.log(performance.now() - now);
var now = performance.now()
for(let i = 5000000; i<5010000; i++) {
a0.includes(i)
}
console.log(performance.now() - now);
平均用时66ms 1385ms 71728ms
原因分析
因为Set的实现方式是使用类似hash table的数据结构;而Array是链表,则需要遍历元素,导致需要遍历的越多,耗时越长。
Set objects must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements in the collection. The data structures used in this Set objects specification is only intended to describe the required observable semantics of Set objects. It is not intended to be a viable implementation model.
WeakSet
WeakSet对象只可以存储对象数据,且只能有唯一值。
尝试插入非对象数据类型时,会触发报错。
var s0 = new WeakSet([1])
// Uncaught TypeError: Invalid value used in weak set
// at WeakSet.add (<anonymous>)
// at new WeakSet (<anonymous>)
// at <anonymous>:1:10
var s0 = new WeakSet()
s0.add('abc')
// Uncaught TypeError: Invalid value used in weak set
// at WeakSet.add (<anonymous>)
// at <anonymous>:1:4
WeakSet vs Set的差异
两者的差异主要集中在两点:
- WeakSet只能存储对象,而Set可以存储任意数据类型
- WeakSet存储的对象,如果没有其他的引用的话,这个对象将会被垃圾回收。这就是冠以 Weak 的原因,同时也意味着,WeakSet是不可枚举的,也就没有size。
在Chrome的开发者工具Console面板试验以下代码:
var a0 = []
for(let i = 0; i<10000000; i++) {
a0.push(i)
}
var s0 = new WeakSet()
s0.add(a0)
a0 = null
console.log(s0)
// output: WeakSet {Array(10000000)}
// 点击Chrome开发者工具的Memory面板中的“Collect garbage“按钮,触发GC机制
s0
// output: WeakSet {}
方法
相对于Set,WeakSet的弱引用特性导致它的方法只有以下3个:
add(value)在末尾添加元素delete(value)删除与这个值相等的元素has(value)返回是否包含这个值
兼容性
最后,来看看浏览器兼容情况。
Set
WeakSet
基本上,绝大部分的现代浏览器都支持Set和WeakSet,绝大部分使用场景都可以放心使用。