第十章 集合

83 阅读4分钟

set集合

set集合中存放的数据都是唯一的,不会发生重复

set集合内部使用Object.is()方法判断两个数据是否一致的,但有一种情况比较特殊,即set集合认为+0和-0是相等的,这两个数据不会使用Object.is()进行判断,而是直接认为两者是相等的

set集合是有“顺序”的,set集合中的数据会按照被添加的先后顺序依次进行排列

set集合是一个可迭代对象

创建set集合

创建一个没有任何内容的set集合

 const set = new Set();

创建一个具有初始内容的set集合,初始内容为可迭代对象每次迭代所得到的结果

 const set = new Set(iterable);

若Set构造函数中传入的是字符串,则函数内部会将该字符串转换为String对象,然后进行迭代(String对象是可迭代的)

 const set = new Set("aaabbccc");
 ​
 console.log(set);       // Set(3) {"a", "b", "c"}

set集合的实例成员

  1. size

    set集合中的元素数量

    该属性是只读的,不能被重新赋值

  2. add(数据)

    添加一个数据到set集合末尾,若添加的数据已经存在,则不进行任何操作

  3. has(数据)

    判断set集合中是否已经存在对应的数据

  4. delete(数据)

    删除set集合中相应的数据,返回是否删除成功

    清空set集合

set集合与数组的相互转换

数组转为set
 const set = new Set(arr);
set转为数组
 const arr = [...set];

遍历set集合

使用for of循环
 for(const item of set){
     console.log(item);
 }
使用set集合的实例方法forEach
 set.forEach((item)=>{
     console.log(item);
 });

set的forEach中的回调函数能够接受三个参数,前两个参数都为当前遍历到的元素,第三个参数是set本身

 const set = new Set(["a", "b", "c"]);
 ​
 set.forEach((i1, i2, s)=>{
     console.log(i1, i2, s);
 });
 ​
 /**
     "a" "a" Set(3) {"a", "b", "c"}
     "b" "b" Set(3) {"a", "b", "c"}
     "c" "c" Set(3) {"a", "b", "c"}
 */

应用:求两个数组的并集、交集、差集

 const arr1 = [1, 1, 2, 3, 4, 4];
 const arr2 = [1, 3, 4, 4, 5, 6];

求并集:

 // 两者组合起来就是并集
 ​
 const arr3 = [...new Set([...arr1, ...arr2])];
 ​
 // arr3 = [1, 2, 3, 4, 5, 6]

求交集:

 // 两者都有的就是交集
 ​
 const set1 = new Set(arr1);
 const set2 = new Set(arr2);
 const arr3 = [...set1].filter((item)=>{
     return set2.has(item);
 });
 ​
 // arr3 = [1, 3, 4]

求差集:

 // 并集中除开交集部分,剩下的就是差集
 ​
 const set1 = new Set(arr1);
 const set2 = new Set(arr2);
 const set3 = new Set([...set1].filter((item)=>{
     return set2.has(item);
 }));
 const arr3 = [...new Set([...arr1, ...arr2])].filter((item)=>{
     return !set3.has(item);
 });
 ​
 // arr3 = [2, 5, 6]

手写set

JS的set实际上是使用C/C++代码写的,因此执行效率更高,这里只是模拟set的实现

 class MySet {
     constructor(iterable = []) {
         // 验证iterator是否是可迭代对象
         if (typeof iterable[Symbol.iterator] !== "function") {
             throw TypeError(`${iterable} is not iterable`);
         }
         this._datas = [];
         for (const item of iterable) {
             this.add(item);
         }
     }
 ​
     add(data) {
         if (!this.has(data)) {
             if (data === 0) {
                 this._datas.push(0);
             } else {
                 this._datas.push(data);
             }
         }
     }
 ​
     has(data) {
         for (const item of this._datas) {
             if ((item === 0 && data === 0) || Object.is(item, data)) {
                 return true;
             }
         }
         return false;
     }
 ​
     delete(data) {
         for (let i = 0; i < this._datas.length; i++) {
             const item = this._datas[i];
             if ((item === 0 && data === 0) || Object.is(item, 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);
         }
     }
 ​
     get size() {
         return this._datas.length;
     }
 }

map集合

map集合用于存储多个键值对数据

使用对象存储键值对存在以下问题:

  1. 键名只能是字符串或符号
  2. 获取数据的数量不方便
  3. 键名容易跟原型上的名称冲突

map中的键可以是任何类型,但也必须保证键唯一,map保证键唯一的方式与set保证数据唯一的方式相同

map集合是一个可迭代对象

map集合中键值对的顺序严格按照第一个加入该键值对的顺序排序

集合无法通过JSON.stringify()进行字符串序列化

创建map集合

创建一个空的map集合

 const map = new Map();

创建一个具有初始内容的map集合,初始内容为可迭代对象每次迭代所得到的结果,但map要求该结果必须是一个长度为2的可迭代对象,对象的第一项表示键,对象的第二项表示值

 const map = new Map(iterable);
 const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
 ​
 console.log(map);       // Map(3) {"a"=>1, "b"=>2, "c"=>3}

map集合的实例成员

  1. size

    map集合中键值对的数量

    该属性是只读的,不能被重新赋值

  2. set(key, value)

    设置一个键值对,键和值可以是任何类型

    如果键不存在,则添加一项到map中

    如果键已存在,则修改键值对的值

  3. get(key)

    得到对应的键的键值对的值

    如果不存在对应的键,则返回undefined

  4. has(key)

    判断是否存在对应的键

    该方法只会判断map集合自身拥有的键

  5. delete(key)

    删除对应的键的键值对,返回是否删除成功

  6. clear()

    清空map集合

map集合与数组的相互转换

数组转为map
 const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
map转为数组
 const arr = [...map];

map转换为的数组,其元素都是[key, value]的形式

 const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
 ​
 const arr = [...map];
 ​
 console.log(arr);       // [["a", 1], ["b", 2], ["c", 3]]

遍历map集合

使用for of循环

每次循环得到的是一个[key, value]形式的数组

 for(const item of map){
     console.log(item);
 }
使用map集合的实例方法forEach

map的forEach中的回调函数能够接受三个参数,第一个参数为每一项的值,第二个参数为每一项的键,第三个参数为map集合本身

 const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
 ​
 map.forEach((value, key, m)=>{
     console.log(value, key, m);
 });
 ​
 /**
     1 "a" Map(3) {"a"=>1, "b"=>2, "c"=>3}
     2 "b" Map(3) {"a"=>1, "b"=>2, "c"=>3}
     3 "c" Map(3) {"a"=>1, "b"=>2, "c"=>3}
 */

手写map

JS的map实际上是使用C/C++代码写的,因此执行效率更高,这里只是模拟map的实现

 class MyMap{
     constructor(iterable = []){
         // 验证iterator是否是可迭代对象
         if (typeof iterable[Symbol.iterator] !== "function") {
             throw TypeError(`${iterable} is not iterable`);
         }
         this._datas = [];
         for(const item of iterable){
             if (typeof item[Symbol.iterator] !== "function") {
                 throw TypeError(`${item} is not iterable`);
             }
             const iterator = item[Symbol.iterator]();
             const key = iterator.next().value;
             const value = iterator.next().value;
             this.set(key, value);
         }
         
     }
     
     set(key, value){
         if(this.has(key)){
             for(const item of this._datas){
                 if((key === 0 && item.key === 0) || Object.is(key, item.key)){
                     item.value = value;
                     break;
                 }
             }
         }else{
             this._datas.push({key, value});
         }
     }
     
     has(key){
         for(const item of this._datas){
             if((key === 0 && item.key === 0) || Object.is(key, item.key)){
                 return true;
             }
         }
         return false;
     }
     
     get(key){
         for(const item of this._datas){
             if((key === 0 && item.key === 0) || Object.is(key, item.key)){
                 return item;
             }
         }
         return undefined;
     }
     
     get size(){
         return this._datas.length;
     }
     
     delete(key){
         for(let i = 0; i < this._datas.length; i++){
             const item = this._datas[i];
             if((key === 0 && item.key === 0) || Object.is(key, item.key)){
                 this._datas.splice(i, 1);
                 return true;
             }
         }
         return false;
     }
     
     clear() {
         this._datas.length = 0;
     }
     
     *[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);
         }
     }
 }

WeakSet和WeakMap

WeakSet

WeakSet和Set的功能基本相同,除了以下几个方面:

  1. WeakSet中保存的引用不会影响垃圾回收

    JS会定期回收一些无用的对象,这些对象是无法通过其他变量或属性找到的(即没有变量或属性保存该对象的地址),这些对象称之为垃圾

    垃圾回收时,会考虑普通set

    let obj = {
        a: 1
    };
    
    const set = new Set();
    
    set.add(obj);
    
    obj = null;
    
    console.log(set);
    

    如上set中保存了该对象的引用,因此尽管obj已经不指向该对象了,该对象也仍然不会被当做垃圾回收掉

    但垃圾回收是不会考虑WeakSet的

    let obj = {
        a: 1
    };
    
    const set = new WeakSet();
    
    set.add(obj);
    
    obj = null;
    
    console.log(set);
    

    尽管WeakSet中还保存有对象的引用,但垃圾回收不考虑WeakSet,因此对象最终还是被回收掉了

  2. WeakSet只能添加对象

  3. WeakSet是不可迭代的

  4. WeakSet没有size属性,没有forEach方法

WeakMap

WeakMap和Map的功能基本相同,除了以下几个方面:

  1. WeakMap的键所保存的引用不会影响垃圾回收

    当某个对象只被WeakMap中的一个键所引用时,该键所属的键值对最终就都会被当作垃圾回收掉

    let obj = {
        a: 1
    };
    
    const map = WeakMap();
    
    map.set(obj, "abcabc");
    
    obj = null;
    
    console.log(map);
    
  2. WeakMap的键必须是对象

  3. WeakMap是不可迭代的

  4. WeakMap没有size属性,没有forEach方法