javascript进阶知识7-Set与WeakSet

182 阅读6分钟

Es6新增的Set是一种新集合类型,为这门语言带来了集合数据结构。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成Set数据结构。

1.Set的构建

使用new关键字和Set构造函数可以创建一个空集合:

const m = new Set();

如果想在初始化的同时初始化实例,则可以给Set构造函数传入一个可迭代对象,其中包括插入到新集合实例中的元素:

const s1 = new Set(['val1', 'val2', 'val3']);
console.log(s1);   // Set(3) { 'val1', 'val2', 'val3' }

初始化之后可以使用add()增加值,使用has()查询,通过size取得元素数量,以及使用delete()clear()删除元素。

add()增加值

const s1 = new Set(['val1', 'val2', 'val3']);
console.log(s1); // Set(3) { 'val1', 'val2', 'val3' }
s1.add('val4');
console.log(s1); // Set(4) { 'val1', 'val2', 'val3', 'val4' }
s1.add('val5').add('val6')
console.log(s1); //Set(6) { 'val1', 'val2', 'val3', 'val4', 'val5', 'val6' }

has()查询

const s1 = new Set(['val1', 'val2', 'val3']);
console.log(s1.has('val3')); // true
console.log(s1.has('val5')); // false

size取得元素数量

const s1 = new Set(['val1', 'val2', 'val3']);
console.log(s1.size); // 3

delete()删除元素

const s1 = new Set(['val1', 'val2', 'val3']);
s1.delete('val1')
console.log(s1.size); // 2
console.log(s1); // Set(2) { 'val2', 'val3' }

clear()销毁集合实例中的所有值

const s1 = new Set(['val1', 'val2', 'val3']);
s1.clear()
console.log(s1.size); // 0
console.log(s1); // Set(0) {}

Set可以包含任何JS数据类型当为值。相当于使用严格对象想等的标准来检查值的匹配性。

const s = new Set();

const fun = function() {};
const obj = {};
const sym = Symbol();

s.add(fun).add(obj).add(sym);

console.log(s.has(fun)); //true
console.log(s.has(obj)); //true
console.log(s.has(sym)); //true

console.log(s.has(function() {})); //false
console.log(s.has({})); //false

2.Set与Array的转换

Set可以与Array进行转换。使用Array.from()可以将Set转换为数组

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

3.Set的遍历

Set结构的实例有四个遍历方法,可以用于遍历成员.

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

keys方法,values方法,entries方法返回的都是遍历器对象。

let s = new Set(['red','green','blue']);
for(let item of s.keys()) {
    console.log(item);  //red green blue
}
for(let item of s.values()) {
    console.log(item);      //red green blue
}
for(let [k,v] of s.entries()) {
    console.log(k,v);      //red red green green blue blue
}
//上面代码中,entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相同。

由于Set结构没有键名,所以keys方法可values方法的行为完全一致。

Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法和keys方法。我们可以省略 value方法和keys方法,直接用for...of循环遍历Set

let set = new Set(['red','green','blue']);
for(let item of set){
    console.log(item);  //red green blue
}

因为values()是默认迭代器,所以可以直接对集合实例使用扩展操作,把集合转换为数组:

const s = new Set(['val1', 'val2', 'val3']);

console.log([...s]); //[ 'val1', 'val2', 'val3' ]

Set结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。

let set = new Set([1,2,3,4,5]);
set.forEach((x) => console.log(x))

上面代码说明,forEach方法的参数就是一个处理函数,该函数的参数与数组的forEach一致,依次为键值,键名,集合本身。

遍历的应用

(1) 利用Set和解构赋值可以很好的对数组进行去重。

let arr = [1, 2, 3, 4, 5, 4, 2, 4, 5]
arr = [...(new Set(arr))]
console.log(arr); // [ 1, 2, 3, 4, 5 ]

(2) 数组的mapfilter方法也可以间接的用在Set上。

let arr = [1, 2, 3, 4, 5];
const set = new Set(arr.map(x => x + 1));
console.log(set); // Set(5) { 2, 3, 4, 5, 6 }

let set2 = new Set([1, 2, 3, 4, 5]);
set2 = new Set([...set2].filter(x => x % 2 === 0));
console.log(set2); // Set(2) { 2, 4 }

使用Set可以很容易的实现并集、交集、差集。

let a = new Set([1,2,3]);
let b = new Set([4,3,2]);
//并集
const union = new Set([...a,...b]);
console.log(union); //{1,2,3,4}
//交集
const intersect = new Set([...a].filter(x => b.has(x)));
console.log(intersect); //{2,3}
//差集
const difference = new Set([...a].filter(x => !b.has(x)));
console.log(difference);    //{1}

4.weakSet

ES6新增的“弱集合”是一种新的集合类型。weakSets Set的兄弟类型,其API也是Set的子集。WeakSet中的weak描述的是JS垃圾回收程序对待“弱集合”中值的方式。

Set的区别:

第一个:WeakSet的成员只能是对象,而不能是其他的值。
第二个:WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象。那么垃圾回收机制会自动会输该对象所占用的额内存,不考虑该对象还存在与WeakSet之中。

let ws = new WeakSet();
ws.add(123);        //报错
 ws.add(Symbol("foo"));  //报错

4.1 构造WeakSet

WeakSet是一个构造函数,可以使用new命令,创建WeakSet数据结构。WeakSet可以接受一个数组或类似数组的对象作为参数(实际上,任何具有Iterator接口的对象,都可以作为WeakSet的参数。)该数组的成员都自动会成为WeakSet实例对象的成员。

const val1 = { id: 1 },
    val2 = { id: 2 },
    val3 = { id: 3 };

const ws = new WeakSet([val1, val2, val3]);
console.log(ws.has(val1)); //true
console.log(ws.has(val2)); //true
console.log(ws.has(val3)); //true

初始化是一个全有或全无的操作,只要有一个值无效就会抛出错误,导致整个初始化失败。

const val1 = { id: 1 },
    val2 = { id: 2 },
    val3 = { id: 3 };

const ws = new WeakSet([val1, val2, 'HAHA']); //Error:Invalid value used in weak set
typeof ws; // ReferenceError:ws is not defined

WeakSet结构的三个方法。

WeakSet.prototype.add(value) :向WeakSet实例添加一个新成员。
WeakSet.prototype.delete(value) :删除WeakSet实例的指定成员。
WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。

const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
console.log(ws.has(window));    //true
console.log(ws.has(foo));       //false
ws.delete(window);
console.log(ws.has(window));    //false

WeakSet没有size属性,没有办法遍历它的成员。也就不能使用for..of、forEach()和其他的keys、values以及entries()方法。

const ws = new WeakSet([[1,2],[3,4]]);
console.log(ws.size);   //undefined
console.log(ws.forEach);    //undefined
console.log(ws.forEach((item) => item));        //报错

WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了,WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。例如:

假如使用普通Set

const disabledElements = new Set();

const loginButton = docuemnt.querySelector('#login');

disabledElements.add(loginButton);

这样,通过查询元素在不在disabledElements中,就可以知道它是否被禁用了。不过,假如元素从DOM树中被删除了,它的应用仍然保存在Set中,因此垃圾回收程序也就不能回收它。

为了让垃圾回收程序回收元素的内存,可以在这里使用WeakSet:

const disabledElements = new WeakSet();

const loginButton = docuemnt.querySelector('#login');

disabledElements.add(loginButton);

这样,只要WeakSet中任何元素从DOM树中被删除,垃圾回收程序也就可以忽略其存在,而立即释放其内存。

(哈哈520也要敲代码哦~~~)