【ES6】Set、Map集合详解与JS模拟

一、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. 键名只能是字符串
  2. 获取数据的数量不方便
  3. 键名容易跟原型上的名称冲突

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一样的功能,不同的是:

  1. 它内部存储的对象地址不会影响垃圾回收
  2. 只能添加对象
  3. 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法

WeakMap

类似于map的集合,不同的是:

  1. 它的键存储的地址不会影响垃圾回收
  2. 它的键只能是对象
  3. 不能遍历(不是可迭代的对象)、没有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>

猜你喜欢

转载自blog.csdn.net/facial_123/article/details/128253284
今日推荐