手写实现 Set 集合,深入理解其核心原理

2022-09-15 03:35:27

Set 集合

从使用方式来看,const set = new Set([1, 2, 3]) 说明 Set 本身是一个构造函数,接受一个数组作为初始值。

除了数组外,还可以传入其他可迭代对象

const set = new Set(new Set([1, 2, 3, 4]))

Set 的具体属性方法就不在这里罗列了,点这里

需要关注一下各方法的返回值:

const set = new Set()

// add() 返回 Set 实例本身,支持链式调用
set.add(1).add(2) // -> Set {1, 2}

// delete() 返回布尔值,表示是否删除成功
set.delete(1) // -> true
set.delete(99) // -> false

// has() 返回布尔值,表示元素是否存在
set.has(2) // -> true
set.has(99) // -> false

/**
 * 迭代器方法
 */
// keys() 返回键的迭代器
set.keys() // -> SetIterator [2]

// values() 返回值的迭代器
set.values() // -> SetIterator [2]

// entries() 返回键值对的迭代器
set.entries() // -> SetIterator [[2, 2]]

// forEach() 没有返回值
set.forEach(value => console.log(value))

实现-步骤 1

Set 的核心特点:

  1. 元素不重复:相同值只会存储一次
  2. 接受可迭代对象:可以不传参数,如果传参数则必须是可迭代对象(像数组、Set、字符串等)

结合这些特点,我们能轻松写出基础结构:

class MySet {
  /**
   * 构造函数
   * @param {Iterable} [iterator] - 可迭代对象,用于初始化 Set
   * @throws {TypeError} 当传入的参数不是可迭代对象时抛出
   */
  constructor(iterator = []) {
    // 检查是否为可迭代对象
    if (typeof iterator[Symbol.iterator] !== 'function') {
      throw new TypeError('iterator is not iterable')
    }

    // 使用数组存储数据
    this._datas = []

    // 遍历可迭代对象,将每个元素添加到 Set 中
    for (const item of iterator) {
      this.add(item)
    }
  }

  add(value) {
    if (!this.has(value)) {
      this._datas.push(value)
    }
    return this
  }

  /**
   * 检查元素是否存在
   * @param {*} value - 要检查的值
   * @returns {boolean} 元素存在返回 true,否则返回 false
   */
  has(value) {
    return this._datas.some((data) => {
      // 处理 +0 和 -0 的情况
      if (data === 0 && value === 0) {
        return true
      }
      return Object.is(data, value)
    })
  }

  /**
   * 判断两个值是否相等
   * @param {*} value1 - 第一个值
   * @param {*} value2 - 第二个值
   * @returns {boolean} 相等返回 true,否则返回 false
   */
  isEquals(value1, value2) {
    // 处理 +0 和 -0 的情况,两者视为相等
    if (value1 === 0 && value2 === 0) {
      return true
    }
    return Object.is(value1, value2)
  }

  delete(value) {
    const originLength = this._datas.length
    // 过滤掉要删除的元素
    this._datas = this._datas.filter(item => !this.isEquals(item, value))
    // 如果长度变化,说明删除成功
    return this._datas.length !== originLength
  }

  get size() {
    return this._datas.length
  }

  clear() {
    this._datas = []
  }
}

实现-步骤 2

在实现了基础结构和方法后,还需要实现迭代器相关的方法。这些方法都返回一个迭代器对象,支持 for...of 循环。

class MySet {
  // ... 之前的代码

  keys() {
    return this._createIterator()
  }

  values() {
    return this._createIterator()
  }

  entries() {
    const value = this._datas
    const iterator = function* () {
      for (const val of value) {
        yield [val, val]
      }
    }
    const it = iterator()
    it[Symbol.iterator] = it
    return it
  }

  forEach(callbackFn, thisArg) {
    const value = this._datas
    for (const val of value) {
      callbackFn.call(thisArg, val, val, this)
    }
  }

  /**
   * 创建迭代器
   * @returns {Iterator} 返回迭代器对象
   * @private
   */
  _createIterator() {
    const value = this._datas

    // 使用生成器函数创建迭代器
    const iterator = function* () {
      for (const val of value) {
        yield val
      }
    }

    const it = iterator()
    // 设置迭代器自身为迭代器方法,支持可迭代协议
    it[Symbol.iterator] = function () {
      return it
    }
    return it
  }
}

Set 是这样的, Map 和这个也差不多。 等待有缘人给补一下 😁