自己实现了一下 Map 和 Set

2023/8/16 Map

自己手写一个 MapSet来练一下类的封装和使用~

# 实现一个MyMap

有人说map的底层是用了两个数组(一个放键名,一个放键值),具体操作我不太清楚,这里我是使用了一个数组来直接存储对象形式的键值对的方式来模拟数据存储的

基本的API就不多赘述了,我在这里学到了迭代器的配置,如果你想让你的类或者对象可以被迭代(可以被for...in遍历出来key),那你就给你的类或对象配置一个 Symbol.iterator,换句话说,如果你的对象是可迭代的,那它一定有一个 Symbol.iterator,注意它是个函数嗷~

MDN上对它的描述如下:

The Symbol. iterator static data property represents the well-known symbol @@iterator. The iterable protocol looks up this symbol for the method that returns the iterator for an object. In order for an object to be iterable, it must have an @@iterator key.

你只需要知道它怎么使用即可👇

const iterable1 = {};

iterable1[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

for (let i of iterable1) {
  console.log(i);
}

// 1, 2, 3

或许直接使用函数的简写形式更方便

const someObj = {
  *[Symbol.iterator]() {
    yield "a";
    yield "b";
  },
};

for (let i of someObj) {
  console.log(i);
}
// a, b

# 代码实现

注释写的很详细了~

class MyMap {
  constructor(iterator) {
    // 判断传入的参数是否是可迭代的 —— 比如二维数组中存的是不是可迭代的 —— 数组
    if (typeof iterator[Symbol.iterator] !== "function") {
      throw new TypeError(`${typeof iterator} ${iterator} is not iterable`);
    }
    // 存储数据
    this.datas = [];

    // 判断存的可迭代对象里的内容是不是可迭代的
    for (const item of iterator) {
      if (typeof item[Symbol.iterator] !== "function") {
        throw new TypeError(`Iterator value ${item} is not an entry object`);
      }
      // 调用set数据
      this.set(...item);
    }
  }

  /**
   * 往map里添加元素
   * @param {any} key
   * @param {any} value
   */
  set(key, value) {
    for (const item of this.datas) {
      // 如果有这个key了
      if (item.key === key) {
        item.value = value;
        return;
      }
    }
    this.datas.push({
      key,
      value,
    });
  }

  /**
   * 根据穿入的键名得到对应的值
   * @param {any} keyName
   * @returns 键名对应的值,如果没有改键则返回 undefined
   */
  get(keyName) {
    for (const { key, value } of this.datas) {
      if (keyName === key) {
        return value;
      }
    }
    return undefined;
  }

  /**
   * 判断map里是否有传入的key
   * @param {any} key
   * @returns {Boolean} 有就返回true 没有就返回false
   */
  has(key) {
    for (const item of this.datas) {
      if (item.key === key) {
        return true;
      }
    }
    return false;
  }

  /**
   * 删除map中的某个键值对
   * @param {any} key
   * @returns {Boolean} 是否删除成功
   */
  delete(key) {
    if (!this.has(key)) {
      return false;
    }

    for (let i = 0; i < this.datas.length; i++) {
      if (this.datas[i].key === key) {
        this.datas.splice(i, 1);
        return true;
      }
    }
  }

  /**
   * 只读的,获取Map的长度
   * @returns {Number} 返回Map的长度
   */
  get size() {
    return this.datas.length;
  }

  /**
   * 清空map
   */
  clear() {
    this.datas = [];
  }

  /**
   * 添加迭代器属性 —— 让我们的Map变成可迭代的
   */
  *[Symbol.iterator]() {
    for (const item of this.datas) {
      yield item;
    }
  }

  /**
   * 添加forEach方法,可以通过forEach来访问
   * @param {Function} callback
   */
  forEach(callback) {
    for (const { key, value } of this.datas) {
      callback(value, key, this);
    }
  }
}

测试一下👇

const test = new MyMap([
  [1, 2],
  [3, 4],
]);

console.log("test: ", test);

console.log(test.get(1));
test.set(1, 3);
test.set("1", 3);
console.log(test);
console.log(test.has("1"));
console.log("size: ", test.size);

/* 
  test:  MyMap { datas: [ { key: 1, value: 2 }, { key: 3, value: 4 } ] }
  2
  MyMap {
    datas: [
      { key: 1, value: 3 },
      { key: 3, value: 4 },
      { key: '1', value: 3 }
    ]
  }
  true
  size:  3
*/

# 实现MySet

Set 相对来说就简单多了,因为一般只有往里存的的操作,而且我们用 Set 通常也是为了去重的需求,所以我们只需要注意将其设置为可迭代的,让它可以与数组随意转换~

# 代码实现

class MySet {
  constructor(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);
    }
  }

  /**
   * 往MySet里添加值
   * @param {any} value
   */
  add(value) {
    if (this.has(value)) {
      return;
    }

    this._datas.push(value);
  }

  /**
   * MySet里是否有传入的参数
   * @param {any} value
   * @returns {boolean} 有就返回true 没有就返回false
   */
  has(value) {
    if (this._datas.indexOf(value) !== -1) {
      return true;
    }
    return false;
  }

  /**
   * 删除传入的值
   * @param {any} value
   */
  delete(value) {
    for (let i = 0; i < this._datas.length; i++) {
      if (this._datas[i] === value) {
        this._datas.splice(i, 1);
        return;
      }
    }
  }

  /**
   * 清空MySet
   */
  clear() {
    this._datas = [];
  }

  /**
   * 只读,返回MySet的长度
   */
  get size() {
    return this._datas.length;
  }

  /**
   * 配置MySet可迭代
   */
  *[Symbol.iterator]() {
    for (let item of this._datas) {
      yield item;
    }
  }
  /**
   * 配置forEach函数
   * @param {function} callback
   */
  forEach(callback) {
    for (let i = 0; i < this._datas.length; i++) {
      callback(this._datas[i], this._datas[i], this);
    }
  }

  /**
   * 判断传入的两个参数是否相等
   * @param {any} value1
   * @param {any} value2
   */

  isEqual(value1, value2) {
    return Object.is(value1, value2);
  }
}

测试一下👇

const set = new MySet([1, 2, 3]);

console.log("set: ", set);
console.log(set.has(1));
set.add(4);
set.add(3);
console.log(set.has(4));
console.log("size: ", set.size);
console.log(...set);

console.log([...set]);
console.log(set.isEqual(1, 1));

/* 
  set:  MySet { _datas: [ 1, 2, 3 ] }
  true
  true
  size:  4
  1 2 3 4
  [ 1, 2, 3, 4 ]
*/
How to love
Lil Wayne