Set
基本API
使用 new 关键字可以创建一个空集合,同时也可以在创建的时候进行初始化
//创建一个空集合
const m1 = new Set()
//创建的同时初始化实例
//使用数组初始化集合
const s1 = new Set(["value1","value2","value3"])
console.log(s1.size) //3
//使用自定义迭代器初始化集合
const s2 = new Set({
[Symbol.iterator]:function*(){
yield "value1";
yield "value2";
yield "value3";
}
})
console.log(s2.size) //3
- 初始化之后可以使用add()增加值,使用has()查询,通过size()取得元素数量,以及delete()和clear()删除元素
与 Map 类似, Set 可以包含任何JS数据类型作为值
const m = new Set()
const functionKey = function () {}
const symbolKey = Symbol()
const objectKey = new Object();
m.add(functionVal)
m.add(symbolVal)
m.add(objectVal)
console.log(m.has(functionVal)) //true
console.log(m.has(symbolVal)) //true
console.log(m.has(objectVal)) //true
顺序与迭代
Set 会维护值插入时的顺序,因此支持按顺序迭代
集合实例可以提供一个迭代器(Iterator),能以插入顺序生成集合内容.可以通过value()方法及其别名方法keys()(或者Symbol.iterator属性,它引用value())取得这个迭代器:
const s = new Set(["value1","value2","value3"])
console.log(s.values === s[Symbol.iterator]) //true
console.log(s.keys === s[Symbol.iterator]) //true
for(let values of s.values()){
alert(values) //value1,value2,value3
}
因为values()是默认迭代器,所以可以直接对集合实例使用扩展操作,把集合转换为数组:
const s = new Set(["val1","val2","vla3","val4"])
console.log([...s])
集合entries()方法返回一个迭代器,可以按照插入顺序产生两个包含元素的数组,这两个元素是集合中每个值的重复出现:
const s = new Set(["vla1", "vla2", "vla3", "vla4", "vla5"]);
for(let m of s.entries()){
console.log(m)
}
//["val1","val1"] ["val2","val2"] ["val3","val3"]
如果不使用迭代器,而是使用回调方法,则可以调用集合的forEach()方法并传入回调,依次迭代每个键/值对.传入的回调接收可选的第二个参数,这个参数用于重写回调内部this的值:
const s = new Set(["value1", "value2", "value3"]);
s.forEach((val,dupVal) => alert(`${val} -> ${dupVal}`))
修改属性的值不会影响其作为集合值的身份:
const s1 = new Set(["value"])
//字符串原始值作为值不会被改变
for(let value of s1.values()){
value = "newValue"
console.log(value) //newValue
console.log(s1.has("value")) //true
}
const valObj = {id:1}
const s2 = new Set([valObj])
//修改值对象的属性,但对象仍然存在于集合中
for(let value of s2.values()){
value.id = "newValue"
console.log(value) //{id: 'newValue'}
console.log(s2.has(valObj)) //true
}
console.log(valObj) //{id: 'newValue'}
- 字符串:在 Set 中,字符串是不可变的,所以修改循环中的变量值不会影响 Set 中的元素。
- 对象:在 Set 中,对象是通过引用存储的,所以修改循环中的变量(实际上是对象的引用)会影响 Set 中的实际对象。
定义正式集合操作
Set 和 Map 相似: 很多开发者喜欢使用Set, 但需要手动实现;或者类似子类化Set,或者定义一个实用函数库.要把两种方式合二为一,可以在子类上实现静态方法,然后在实例方法中使用这些静态方法.
- 某些Set操作是有关联性的,因此最好让实现的方法能支持处理多个集合实例
- Set 保留插入顺序,所有返回的集合必须保证顺序
- 尽可能高效地实用内存,扩展操作符的语法很简洁,但尽可能避免集合和数组间的相互转换能够节省对象初始化成本
- 不要修改已有的集合实例.union(a,b)或a.union(b)应该返回包含结果的新集合实例.
class XSet extends Set {
union(...sets) {
return XSet.union(this, ...sets);
}
intersection(...sets) {
return XSet.intersection(this, ...sets);
}
difference(...sets) {
return XSet.difference(this, ...sets);
}
symmetricDifference(set) {
return XSet.symmetricDifference(this, set);
}
cartesianProduct(sets) {
return XSet.cartesianProduct(this, sets);
}
powerSet() {
return XSet.powerSet(this);
}
// 并集
static union(a, ...bSets) {
const unionSet = new XSet(a);
for (const b of bSets) {
for (const bValue of bSets) {
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 symmetricDifference(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]);
}
}
}
// 幂集
static powerSet(a) {
const powerSet = new XSet([new XSet()]);
for (const aValue of a) {
for (const set of new XSet(powerSet)) {
powerSet.add(new XSet(set).add(aValue))
}
}
return powerSet;
}
}
WeakSet
基本API
可以使用 new 关键字来实例化一个空的 WeakSet
const m = new WeakSet()
弱集合中的值只能是Object或者继承自Object的类型
- 可以使用数组初始化弱集合
- 初始化之后可以使用add()添加新值,has()查询,还可以使用delete()删除
- add()方法返回弱集合实例,所以可以把多个操作连缀起来
弱值
WeakSet 表示弱集合的值,这些值不属于正式的引用,不会阻止垃圾回收
add()方法初始化了一个新对象,并将他用作一个值.因为没有指向这个对象的其他引用,所以当这行代码执行完毕之后,这个对象的值就会被当作垃圾回收.然后这个值就从弱集合中消失了,使其成为一个空集合:
const m = new WeakSet()
m.add({})
container对象维护着一个对弱集合值的引用,因此这个对象不会成为垃圾回收的目标,不过,如果调用了rem(),就会摧毁值对象的最后一个引用,垃圾回收程序就可以把这个值清理掉
const m = new WeakSet()
const container = {
val:{}
}
m.add(container.val)
function rem(){
container.val = null
}
不可迭代值
因为 WeakSet 中的值任何时候都可能被销毁,所以没必要提供迭代其值的能力
WeakSet 之所以限制只能用对象作为值,是为了保证只有通过值对象的引用才能取得值.
使用弱集合
相比于WeakSet 实例,WeakSet 实例的用处没有那么大.