一、set 集合
一直以来,JS只能使用数组和对象来保存多个数据,缺乏像其他语言那样拥有丰富的集合类型。因此,ES6新增了两种集合类型(set 和 map),用于在不同的场景中发挥作用。
1)set用于存放不重复的数据
1)如何创建set集合
new Set(); // 创建一个没有任何内容的set集合
new Set(iterable); // 创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果
2)如何对set集合进行后续操作
- add(数据): 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作
- set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
- has(数据): 判断set中是否存在对应的数据
- delete(数据):删除匹配的数据,返回是否删除成功
- clear():清空整个set集合
- size: 获取set集合中的元素数量,只读属性,无法重新赋值
3)如何与数组进行相互转换
const s = new Set([x,x,x,x,x]);
// set本身也是一个可迭代对象,每次迭代的结果就是每一项的值
const arr = [...s];
4)如何遍历
- 使用for-of循环
- 使用set中的实例方法forEach
注意:set集合中不存在下标,因此forEach中的回调的第二个参数和第一个参数是一致的,均表示set中的每一项
2)Set集合的应用
求两个数组的并集、交集
const arr1 = [33, 43, 55, 66, 11, 33, 5, 11];
const arr2 = [44, 22, 3, 11, 45, 66, 78, 2, 23];
// 求并集
const allArr = [...new Set([...arr1, ...arr2])];
// 求交集
const noRepeatArr1 = new Set(arr1);
noRepeatArr1.forEach((item) => {
if (arr2.includes(item)) {
return;
}
noRepeatArr1.delete(item);
});
console.log([...noRepeatArr1]);
二、手写set
代码如下,纯手写实现相应功能
// 模拟Set集合
class MySet {
constructor(iterator = []) {
// 判断iterator是否为可迭代对象
if (typeof iterator[Symbol.iterator] !== "function") {
throw new TypeError(`${typeof iterator} ${iterator} is not iterable`);
}
this._datas = [];
for (const item of iterator) {
this.add(item);
}
}
/**
* 判断集合中是否有data数据
* @param {any} data
*/
has(data) {
for (const item of this._datas) {
if (this.isEqual(item, data)) {
return true;
}
}
return false;
}
/**
* 删除集合中的数据
* @param {any} data
*/
delete(data) {
for (const item of this._datas) {
if (this.isEqual(data, item)) {
this._datas.splice(this._datas.indexOf(data), 1);
return true;
}
}
return false;
}
/**
* 只读size属性
*/
get size() {
return this._datas.length;
}
/**
* 清空集合
*/
clear() {
this._datas = [];
}
/**
* 向集合中添加一个数据
* @param {any} data 数据
*/
add(data) {
if (this.has(data)) {
return;
}
this._datas.push(data);
}
/**
* 循环函数
* @param {any} callback 回调函数
*/
forEach(callback) {
for (let i = 0; i < this._datas.length; i++) {
callback(this._datas[i], this._datas[i], this);
}
}
/**
* 将其变为生成器函数
*/
*[Symbol.iterator]() {
for (const item of this._datas) {
yield item;
}
}
/**
* 判断两个数是否相等,不限类型
* @param {any} value1
* @param {any} value2
*/
isEqual(value1, value2) {
if (value1 === 0 && value2 === 0) {
return true;
} else {
return Object.is(value1, value2);
}
}
}
测试代码如下所示
const s = new MySet([1, 2, 3, 4, 3]);
s.forEach((a1, a2, a3) => {
console.log(a1, a2, a3);
});
三、map集合
键值对(key value pair)数据集合的特点:键不可重复
map集合专门用于存储多个键值对数据。
在map出现之前,我们使用的是对象的方式来存储键值对,键是属性名,值是属性值。
使用对象存储有以下问题:
- 键名只能是字符串
- 获取数据的数量不方便
- 键名容易跟原型上的名称冲突
1)如何创建map
new Map(); //创建一个空的map
new Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
每一次迭代的结果,不一定必须是一个长度为2的数组,但必须是一个可以迭代两次的数据类型。
const mp1 = new Map([["a", 0], [3, 2], [{}, 9]);
2)如何进行后续操作
- size:只读属性,获取当前map中键的数量
- set(键, 值):设置一个键值对,键和值可以是任何类型
- 如果键不存在,则添加一项
- 如果键已存在,则修改它的值
- 比较键的方式和set相同(注意{}与{}属于两个对象)
- get(键): 根据一个键得到对应的值
- has(键):判断某个键是否存在
- delete(键):删除指定的键
- clear(): 清空map
3)和数组互相转换
和set一样
4)遍历
- for-of,每次迭代得到的是一个长度为2的数组
- forEach,通过回调函数遍历
- 参数1:每一项的值
- 参数2:每一项的键
- 参数3:map本身
四、手写map
以下是我的手撕代码,完全模仿Map集合的性质完成,也完全是自己完成的
class MyMap {
constructor(iterator) {
// Whether the first of iterator is iterable or not.
if (typeof iterator[Symbol.iterator] !== "function") {
// The first floor of iterator is not iterabl, throw an Error of Type.
throw new TypeError(`${typeof iterator} ${iterator} is not iterable`);
}
// Create an object to store Map datas
this._datas = [];
// The first floor of iterator is iterable
for (const item of iterator) {
// Whether the second floor of iterator is iterable or not
if (typeof item[Symbol.iterator] !== "function") {
// The second floor of iterator is not iterable, throw an Error of Type.
throw new TypeError(`Iterator value ${item} is not an entry object`);
}
// The parameter "iterator" is valid.
this.set(...item);
}
}
/**
* Is there a key named "key" in MyMap? This is a judging function.
* @param {any} key
* @returns
*/
has(key) {
for (const item of this._datas) {
if (item.key === key) {
return true;
}
}
return false;
}
/**
* Get MyMap item's value according to keyName.
* @param {any} keyName
* @returns value or undefined
*/
get(keyName) {
for (const { key, value } of this._datas) {
if (keyName === key) {
return value;
}
}
return undefined;
}
/**
* Set the map data occording to "key" and "value"
* @param {any} key
* @param {any} value
*/
set(key, value) {
// Whether object {key, value} is still in Array this._datas or not
for (const item of this._datas) {
if (item.key === key && item.value === value) {
// Yes. There is
return;
}
// No. There isn't.
// Then judge whether object includes "key" has already in Array this._datas
if (item.key === key) {
// Yes. It is. And Modify the value of key
item.value = value;
return;
}
}
// No!And then add it normally
this._datas.push({
key,
value,
});
}
/**
* Delete key in MyMap
* @param {any} key
* @returns {Boolean} Delete successfully?
*/
delete(key) {
// Whether there is key in this._datas
if (!this.has(key)) {
return false;
}
// Find the item with "key" and then delete it.
for (let i = 0; i < this._datas.length; i++) {
// Find the item with "key"
if (this._datas[i].key === key) {
this._datas.splice(i, 1);
return true;
}
}
}
/**
* Read only. Get the number of keys in MyMap
* @returns {Number} The number of keys in MyMap
*/
get size() {
return this._datas.length;
}
/**
* Clear the items in MyMap
*/
clear() {
this._datas = [];
}
/**
* Add iterable attribute for MyMap. Now the example of MyMap can be iterable.
*/
*[Symbol.iterator]() {
for (const item of this._datas) {
yield item;
}
}
/**
* forEach loop function, you can do what you want in callback function!
* @param {Function} callback
*/
forEach(callback) {
for (const { key, value } of this._datas) {
callback(value, key, this);
}
}
}
测试代码如下
const mp = new MyMap([
[1, 2],
["a", 3],
]);
// for (const item of mp) {
// console.log(item);
// }
mp.forEach((a1, a2, a3) => {
console.log(a1, a2, a3);
});
五、WeakSet 和 WeakMap
这两个关键字被制造出来,其实可以认为是为了监测对象是否还存在的,就是不影响对象的垃圾回收机制。
WeakSet
使用该集合,可以实现和set一样的功能,不同的是:
- 它内部存储的对象地址不会影响垃圾回收
- 只能添加对象
- 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法
WeakMap
类似于map的集合,不同的是:
- 它的键存储的地址不会影响垃圾回收
- 它的键只能是对象
- 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>
<!-- { id:"1", name:"姓名1" } -->
<li>1</li>
<!-- { id:"2", name:"姓名2" } -->
<li>2</li>
<!-- { id:"3", name:"姓名3" } -->
<li>3</li>
</ul>
<script>
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);
</script>
</body>
</html>