11-1. set 集合
一直以来,JS只能使用数组和对象来保存多个数据,缺乏像其他语言那样拥有丰富的集合类型。因此,ES6新增了两种集合类型(set
和 map
),用于在不同的场景中发挥作用。
set用于存放不重复的数据
- 如何创建
set
集合
new Set(); //创建一个没有任何内容的set集合
new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果
const s1 = new Set();
console.log(s1); // Set(0) {}
// 可以对数组与字符串进行去重
const s2 = new Set("asdfasfasf");
console.log(s2); // Set(4) {"a", "s", "d", "f"}
const s2 = new Set([1,2,3,4,4,3,2,1,5]);
console.log(s2); // Set(5) {1, 2, 3, 4, 5}
- 如何对set集合进行后续操作
add(数据)
: 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作- set使用
Object.is
的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
- set使用
has(数据)
: 判断set中是否存在对应的数据delete(数据)
:删除匹配的数据,返回是否删除成功
-clear()
:清空整个set集合size
: 获取set集合中的元素数量,只读属性,无法重新赋值
const s1 = new Set();
s1.add(1);
s1.add(2);
s1.add(3);
s1.add(1); //无效
s1.add(+0);
s1.add(-0); //无效
console.log(s1); // Set(4) {1, 2, 3,0}
s1.has(3); //true
s1.has(5); // false
s1.delete(3); // 返回 true
console.log(s1); // Set(3) {1, 2,0}
s1.delete(6); // 返回 false 表示没有删除成功,因为该数据不存在。。
s1.clear(); //清空整个set集合
console.log(s1); // Set(0) {}
console.log("总数为:", s1.size); // 0
- 如何与数组进行相互转换
const s = new Set([x,x,x,x,x]);
// set本身也是一个可迭代对象,每次迭代的结果就是每一项的值
const arr = [...s];
数组与字符串去重,把去重后的保存到一个新数组(字符串)中;
const arr = [45, 7, 2, 2, 34, 46, 6, 57, 8, 55, 6, 46];
const result = [...new Set(arr)];
console.log(result); // [45, 7, 2, 34, 46, 6, 57, 8, 55]
const str = "asf23sdfgsdgfsafasdfasfasfasfsafsagfdsfg";
const s = [...new Set(str)].join("");
console.log(s);
-
如何遍历
1). 使用
for-of
循环
2). 使用set中的实例方法forEach
注意:set集合中不存在下标,因此forEach中的回调的第二个参数和第一个参数是一致的,均表示set中的每一项
const s1 = new Set();
s1.add(1);
s1.add(2);
s1.add(3);
s1.add(1); //无效
s1.add(+0);
s1.add(-0); //无效
// for (const item of s1) {
// console.log(item); // {1, 2, 3, 0}
// }
//s1.forEach(item => {
// console.log(item); // {1, 2, 3, 0}
// })
s1.forEach((item, index, s) => {
console.log(item, index, s);
})
11-2. set 应用
// 两个数组的并集、交集、差集 (不能出现重复项),得到的结果是一个新数组
const arr1 = [33, 22, 55, 33, 11, 33, 5];
const arr2 = [22, 55, 77, 88, 88, 99, 99];
//并集
// const result = [...new Set(arr1.concat(arr2))];
console.log("并集", [...new Set([...arr1, ...arr2])]);
const cross = [...new Set(arr1)].filter(item => arr2.indexOf(item) >= 0);
//交集
console.log("交集", cross)
//差集
// console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => arr1.indexOf(item) >= 0 && arr2.indexOf(item) < 0 || arr2.indexOf(item) >= 0 && arr1.indexOf(item) < 0))
console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => cross.indexOf(item) < 0))
11-3. 手写set
class MySet {
constructor(iterator = []) {
//验证是否是可迭代的对象
if (typeof iterator[Symbol.iterator] !== "function") {
throw new TypeError(`你提供的${iterator}不是一个可迭代的对象`)
}
this._datas = [];
for (const item of iterator) {
this.add(item);
}
}
get size() {
return this._datas.length;
}
add(data) {
if (!this.has(data)) {
this._datas.push(data);
}
}
has(data) {
for (const item of this._datas) {
if (this.isEqual(data, item)) {
return true;
}
}
return false;
}
delete(data) {
for (let i = 0; i < this._datas.length; i++) {
const element = this._datas[i];
if (this.isEqual(element, data)) {
//删除
this._datas.splice(i, 1);
return true;
}
}
return false;
}
clear() {
this._datas.length = 0;
}
*[Symbol.iterator]() {
for (const item of this._datas) {
yield item;
}
}
forEach(callback) {
for (const item of this._datas) {
callback(item, item, this);
}
}
/**
* 判断两个数据是否相等
* @param {*} data1
* @param {*} data2
*/
isEqual(data1, data2) {
if (data1 === 0 && data2 === 0) {
return true;
}
return Object.is(data1, data2);
}
}
11-4. map集合
map集合专门用于存储多个键值对数据。
在map出现之前,我们使用的是对象的方式来存储键值对,键是属性名,值是属性值。
键值对(key value pair)数据集合的特点:键不可重复
之前使用对象存储会有以下问题:
- 键名只能是字符串
- 获取数据的数量不方便
- 键名容易跟原型上的名称冲突
1.如何创建map
new Map(); //创建一个空的map
new Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,
//它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
const mp1 = new Map();
console.log(mp1); // Map(0) {}
const mp2 = new Map([["a", 3], ["b", 4], ["c", 5]]);
console.log(mp2); // Map(3) {"a" => 3, "b" => 4, "c" => 5}
- 如何进行后续操作
size
:只读属性,获取当前map中键的数量set(键, 值)
:设置一个键值对,键和值可以是任何类型- 如果键不存在,则添加一项
- 如果键已存在,则修改它的值
- 比较键的方式和set相同 (
Object.is
)
get(键)
: 根据一个键得到对应的值has(键)
:判断某个键是否存在delete(键)
:删除指定的键clear()
: 清空map
const mp2 = new Map([["a", 3], ["b", 4], ["c", 5]]);
const obj = {};
mp2.set(obj, 6456); // Map(4) {"a" => 3, "b" => 4, "c" => 5, {…} => 6456}
mp2.set("a", "abc"); // Map(4) {"a" => "abc", "b" => 4, "c" => 5, {…} => 6456}
mp2.set(obj, 111); // Map(4) {"a" => "abc", "b" => 4, "c" => 5, {…} => 111}
console.log(mp2)
console.log("总数:", mp2.size); // 总数:3
console.log(mp2.get("a")); // abc
console.log(mp2.get("d")); // undefined
console.log(mp2.has("a")); // true
console.log(mp2.delete("a")); // true Map(3) {"b" => 4, "c" => 5, {…} => 111}
console.log(mp2.clear()); // Map(0) {}
- 和数组互相转换 (和set一样)
const mp = new Map([
["a", 3],
["c", 10],
["b", 4],
["c", 5]
]);
const result = [...mp]
console.log(result); //0: (2) ["a", 3]
//1: (2) ["c", 5]
//2: (2) ["b", 4]
- 遍历
for-of
,每次迭代得到的是一个长度为2的数组forEach
,通过回调函数遍历- 参数1:每一项的值
- 参数2:每一项的键
- 参数3:map本身
const mp = new Map([
["a", 3],
["c", 10],
["b", 4],
["c", 5]
]);
for (const item of mp) {
console.log(item ); //["a", 3]
//["c", 5]
//["b", 4]
}
for (const [key, value] of mp) { // 解构了
console.log(key, value) // a 3
// c 5
// b 4
}
mp.forEach((value, key, mp) => {
console.log(value, key, mp);
})
11-5. 手写map
class MyMap {
constructor(iterable = []) {
//验证是否是可迭代的对象
if (typeof iterable[Symbol.iterator] !== "function") {
throw new TypeError(`你提供的${iterable}不是一个可迭代的对象`)
}
this._datas = [];
for (const item of iterable) {
// item 也得是一个可迭代对象
if (typeof item[Symbol.iterator] !== "function") {
throw new TypeError(`你提供的${item}不是一个可迭代的对象`);
}
const iterator = item[Symbol.iterator]();
const key = iterator.next().value;
const value = iterator.next().value;
this.set(key, value);
}
}
set(key, value) {
const obj = this._getObj(key);
if (obj) {
//修改
obj.value = value;
}
else {
this._datas.push({
key,
value
})
}
}
get(key) {
const item = this._getObj(key);
if (item) {
return item.value;
}
return undefined;
}
get size() {
return this._datas.length;
}
delete(key) {
for (let i = 0; i < this._datas.length; i++) {
const element = this._datas[i];
if (this.isEqual(element.key, key)) {
this._datas.splice(i, 1);
return true;
}
}
return false;
}
clear() {
this._datas.length = 0;
}
/**
* 根据key值从内部数组中,找到对应的数组项
* @param {*} key
*/
_getObj(key) {
for (const item of this._datas) {
if (this.isEqual(item.key, key)) {
return item;
}
}
}
has(key) {
return this._getObj(key) !== undefined;
}
/**
* 判断两个数据是否相等
* @param {*} data1
* @param {*} data2
*/
isEqual(data1, data2) {
if (data1 === 0 && data2 === 0) {
return true;
}
return Object.is(data1, data2);
}
*[Symbol.iterator]() {
for (const item of this._datas) {
yield [item.key, item.value];
}
}
forEach(callback) {
for (const item of this._datas) {
callback(item.value, item.key, this);
}
}
}
11-6.WeakSet 和 WeakMap
WeakSet
使用该集合,可以实现和set
一样的功能,不同的是:
- 它内部存储的对象地址不会影响垃圾回收
- 只能添加对象
- 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法
let obj = {
name: "yj",
age: 18
};
let obj2 = obj;
const set = new Set();
set.add(obj);
obj = null;
obj2 = null;
console.log(set); // WeakSet {{value: {name: "yj", age: 18}}}
let obj = {
name: "yj",
age: 18
};
let obj2 = obj;
const set = new WeakSet();
set.add(obj);
obj = null;
obj2 = null;
console.log(set); // WeakSet {{…}} 空
WeakMap
类似于map
的集合,不同的是:
- 它的键存储的地址不会影响垃圾回收
- 它的键只能是对象
- 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法
<ul>
<!-- { id:"1", name:"姓名1" } -->
<li>1</li>
<!-- { id:"2", name:"姓名2" } -->
<li>2</li>
<!-- { id:"3", name:"姓名3" } -->
<li>3</li>
</ul>
const wmap = new WeakMap();
let lis = document.querySelectorAll("li");
for (const li of lis) {
wmap.set(li, {
id: li.innerHTML,
name: `姓名${li.innerHTML}`
});
}
lis[0].remove();
lis = null;
console.log(wmap);