• JS手写set与map


    set

    set是一个没有重复元素的集合,事实上我们无法完全的使用js来模拟出set的全部功能,因为浏览器原生的set底层是使用c++实现,能直接访问内存,所以我们只能实现set的一部分功能
    这里我们使用class来实现
    具体关于class的部分可以看我这篇文章
    class

    add

    首先我们有了一个mySet类,这个类会接受一个可迭代对象,我们需要判断这个可迭代对象是否真的可迭代
    判断依据就是每个可迭代对象内都有一个[@@iterator]成员,并且这个成员还是一个方法

    class mySet {
        constructor(iterator) {
            if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接下来我们定义一个数组来存储相关数据,在构造方法内遍历这个可迭代对象,将其中的内容通过add方法填入数组中

    class mySet {
        #data = []
        constructor(iterator) {
            if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
            for (const item of iterator) {
                this.add(item)
            }
        }
        add(item) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    has与equals

    set与普通数组最大不同的一点就在于set内的元素是不会重复的,所以我们在向数组中放入元素时需要提前判断一下该元素是否已在数组中
    我们可以通过调用has方法来判断此元素是否在数组中

    class mySet {
        #data = []
        constructor(iterator) {
            if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
            for (const item of iterator) {
                this.add(item)
            }
        }
        add(item) {
            if (!this.has(item)) this.#data.push(item)
        }
        has(item) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    现在问题转变成了has方法怎么实现
    我们可以遍历data数组,将传入的值通过Object.is来判断是否相同,如果相同就返回true,全部遍历完都没有返回的话就返回false
    但是Object.is有一个问题,在Object.is中+0和-0被认为是两个不同的东西,所以我们需要将Object.is再封装一层,这个封装可有可无,但set内部是针对Object.is封装了的

    class mySet {
        add(item) {
            if (!this.has(item)) this.#data.push(item)
        }
        has(item) {
            for (const iterator of this.#data) {
                if (mySet.#equal(item, iterator)) {
                    return true
                }
            }
            return false;
        }
        static #equal(item1, item2) {
            if (item1 === 0 && item2 === 0) {
                return false
            }
            return Object.is(item1, item2)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们封装了一个静态方法equal用来比较传入的两个值是否相等

    delete

    接下来我们就需要实现delete方法

    class mySet {
        delete(item) {
            for (const iterator of this.#data) {
                if (mySet.#equal(item, iterator)) {
                    this.#data.splice(i, 1)
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    迭代器

    我们知道set是能直接被forof遍历的,这说明了set内部是实现了迭代器,
    迭代器(iterator),可以把它当做是一个接口,用户可以使用该接口来遍历数据而不需关心数据内部的实现细节
    具体关于迭代器的部分可以看我这篇博客
    迭代器与生成器

    class mySet {
        *[Symbol.iterator]() {
            yield* Object.entries(this.#data)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    完整实现

    我们接下来还有一些成员没有实现,如size,clear,forEach成员,具体代码如下

    class mySet {
        #data = []
        constructor(iterator) {
            if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
            for (const item of iterator) {
                this.add(item)
            }
        }
        add(item) {
            if (!this.has(item)) this.#data.push(item)
        }
        has(item) {
            for (const iterator of this.#data) {
                if (mySet.#equal(item, iterator)) {
                    return true
                }
            }
            return false;
        }
        delete(item) {
            for (const iterator of this.#data) {
                if (mySet.#equal(item, iterator)) {
                    this.#data.splice(i, 1)
                }
            }
        }
        clear() {
            this.#data.length = 0
        }
        forEach(fn) {
            for (let i = 0; i < this.#data.length; i++) {
                const item = this.#data[i]
                fn(item, i, this)
            }
        }
        *[Symbol.iterator]() {
            yield* Object.entries(this.#data)
        }
        get size() {
            return this.#data.length
        }
        static #equal(item1, item2) {
            if (item1 === 0 && item2 === 0) {
                return false
            }
            return Object.is(item1, item2)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    测试代码

    const myset = new mySet([12, 3, 56, 56, 5, 5]);
    console.log(myset)
    const set = new Set([1, 2, 3, 4, 5])
    for (const iterator of myset) {
        console.log(iterator)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结果
    结果

    map

    map和set类似,这里只描述map与set的不同点

    1. 在set中我们只需要针对constructor中传入的对象进行判断,而在map中我们需要针对对象中的各个元素进行判断是否可迭代

      constructor(iterator) {
          if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
          for (const item of iterator) {
              if (typeof item[Symbol.iterator] !== "function") {
                  throw Error(`${item}不是可迭代对象`)
              }
              const iterator = item[Symbol.iterator]();
              const key = iterator.next().value;
              const value = iterator.next().value;
              this.set({
                  key,
                  value
              })
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      如果符合条件的话就将它的第一项拿出作为key,第二项拿出作为value,因为不能确定每个元素都是什么类型,所以只能通过迭代器去拿到相应的值

    2. 在set中我们需要大量的遍历,我们可以封装一个函数用来获取data中的元素

      #getObj(key) {
      
          for (const item of this.#data) {
              if (myMap.#equal(item.key, key)) {
                  return item
              }
          }
          return undefined
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      这个函数传入一个key,如果有的话返回该元素,没有的话就返回undefined,之后其他方法如果需要遍历数组来拿到元素,就可以使用这个方法

    3. 在构建迭代器的时候我们需要知道数组里存的元素是对象,在迭代的时候我们需要将它变为二维数组

      *[Symbol.iterator]() {
          for (const item of this.#data) {
              yield [item.key, item.value]
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

    最终代码

    class myMap {
        #data = []
        constructor(iterator) {
            if (typeof iterator[Symbol.iterator] !== "function") throw Error(`${iterator}不是可迭代对象`)
            for (const item of iterator) {
                if (typeof item[Symbol.iterator] !== "function") {
                    throw Error(`${item}不是可迭代对象`)
                }
                const iterator = item[Symbol.iterator]();
                const key = iterator.next().value;
                const value = iterator.next().value;
                this.set({
                    key,
                    value
                })
            }
        }
        set(item) {
            const obj = this.#getObj(item.key)
            if (this.has(item.key)) {
                obj.value = item.value
            } else {
                this.#data.push(item)
            }
        }
        has(key) {
            const item = this.#getObj(key)
            return item ? true : false
        }
        get(key) {
            return this.#getObj(key);
        }
        delete(key) {
            this.#data = this.#data.filter((value) => {
                if (value.key !== key) return value
            })
        }
        forEach(fn) {
            for (const item of this.#data) {
                fn(item.value, item.key, this)
            }
        }
        #getObj(key) {
            for (const item of this.#data) {
                if (myMap.#equal(item.key, key)) {
                    return item
                }
            }
            return undefined
        }
        static #equal(item1, item2) {
            if (item1 === 0 && item2 === 0) {
                return false
            }
            return Object.is(item1, item2)
        }
        *[Symbol.iterator]() {
            for (const item of this.#data) {
                yield [item.key, item.value]
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    测试代码

    const mymap = new myMap([[1, 'a'], [{ name: "a" }, 2], [null, 3], [undefined, 4], [5, null]])
    console.log(mymap.get(null))
    mymap.delete(undefined)
    for (const item of mymap) {
        console.log(item)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结果

  • 相关阅读:
    Word控件Spire.Doc 【表单域】教程(二):在 C# 中填写 Word 文档中的表单字段
    安装virt-manger虚拟机管理器
    一篇论文回顾 Sora 文生视频技术的背景、技术和应用。
    Ubuntu20.04安装 nginx1.23.1
    ffmpeg命令分析-yuv封装mp4
    wind量化交易接口怎么用?
    ffmpeg编译so
    基于ASP.NET MVC + Bootstrap的仓库管理系统
    掌握 Go 的计时器
    基于单目相机的2D测量(工件尺寸和物体尺寸)
  • 原文地址:https://blog.csdn.net/qq_46244470/article/details/138171052