# Map

ES6Map相比于ES5以前使用对象做哈希映射,功能要强大的多。

使用对象做哈希有一个天生的劣势,只能支持String类型的Key(ES6以后,可以支持Symbol类型的Key),如果传入的数据不是String类型,会被强制转换成String,在有些时候就比较鸡肋了,所以使用Map是最好的选择。

使用ES5实现Map就只能使用数组来进行存储,需要维持MapKey列表和Value列表关心,在每次查找的过程中,通过数组方法的全等比较,同时,还要注意NaN这个特殊类型的处理(在ES6中,多次向Map添加KeyNaN的值,仅会视为一条记录)。

需要注意的点是,如果实现过程中需要用缓存Key来提高使用效率的话,因为Map键的特殊性,必须要使用一个内部的哨兵对象来初始化它的值,不能使用null,也不能使用undefined

最后,只需要按照规格实现Map的迭代器即可。

以下是一个ES6环境下Map的实现,但是因为Symbol的唯一值特性,所以无法支持使用Symbol作为Key的场景。

const map = new MyMap();
const s = Symbol("map");
map.set(s, "hello world");
map.get(s); // undefined

主要原因就是在比较Key的时候,Symbol既无法像普通对象那样比较地址,也无法像基础类型变量那样比较值,所导致的。

function GenerateIterator(keys, values, selector) {
  const SentinelArray = [];

  const render = (key, value) => {
    return {
      key,
      value,
    };
  };

  class MyMapIterator {
    _index = -1;

    _keys = SentinelArray;

    _values = SentinelArray;

    _selector = render;

    [Symbol.iterator]() {
      return this;
    }

    constructor(keys, values, selector) {
      this._keys = keys;
      this._values = values;
      this._index = 0;
      this._selector = selector || render;
    }

    next() {
      var index = this._index;
      if (index >= 0 && index < this._keys.length) {
        var result = this._selector(this._keys[index], this._values[index]);
        if (index + 1 >= this._keys.length) {
          this.initIterator();
        } else {
          this._index++;
        }
        return { value: result, done: false };
      }
      return { value: undefined, done: true };
    }

    return(value) {
      if (this._index >= 0) {
        this.initIterator();
      }
      return {
        value,
        done: true,
      };
    }

    throw(reason) {
      if (this._index >= 0) {
        this.initIterator();
      }
      throw reason;
    }

    initIterator() {
      this._index = -1;
      this._keys = SentinelArray;
      this._values = SentinelArray;
      this._selector = render;
    }
  }

  return new MyMapIterator(keys, values, selector);
}

function MyMap(iterator) {
  const SentinelKey = {};

  class MyMapImplement {
    cachedKey = SentinelKey;

    cachedKeyIdx = -1;

    // 存储keys
    storageKeys = [];
    // 存储值
    storageContents = [];

    // 需要为Map对象部署一个迭代器
    [Symbol.iterator]() {
      return GenerateIterator(this.storageKeys, this.storageContents);
    }

    get size() {
      return this.storageKeys.length;
    }

    constructor(iterator) {
      // 如果有初始化参数,必须是一个迭代器
      const values =
        iterator && typeof iterator[Symbol.iterator] === "function"
          ? [...iterator]
          : [];
      values.length &&
        values.forEach(([key, value]) => {
          this.set(key, value);
        });
    }

    /**
     * 删除Map中的key
     * @param {any} key
     */
    delete(key) {
      // 命中缓存,直接返回缓存值
      const cacheIdx = this._getCache(key);
      // 如果命中缓存值,快速删除
      if (cacheIdx > -1) {
        // 删除key
        this.storageKeys.splice(cacheIdx, 1);
        // 删除val
        this.storageContents.splice(cacheIdx, 1);
        // 初始化缓存
        this._initCache();
        return true;
      } else {
        const idx = this._find(key);
        if (idx > -1) {
          // 删除key
          this.storageKeys.splice(idx, 1);
          // 删除val
          this.storageContents.splice(idx, 1);
        }
        return idx > -1;
      }
    }

    /**
     * 设置值
     * @param {any} key
     * @param {any} value
     * @returns { boolean }
     */
    set(key, value) {
      // 如果存在键值,更新
      if (this.has(key)) {
        const cachedKeyIdx = this.cachedKeyIdx;
        this.storageContents[cachedKeyIdx] = value;
        this.storageKeys[cachedKeyIdx] = key;
      } else {
        // 以最后一个作为缓存
        this.cachedKey = key;
        this.cachedKeyIdx = this.storageKeys.length;
        this.storageKeys.push(key);
        this.storageContents.push(value);
      }
    }

    /**
     * 检测Map中是否包含某个值
     * @param {any} key
     */
    has(key) {
      return this._find(key) > -1;
    }

    /**
     * 获取值
     * @param {any} key
     * @returns {any}
     */
    get(key) {
      // 命中缓存,直接返回缓存值
      const cacheIdx = this._getCache(key);
      if (cacheIdx > -1) {
        return this.cachedVal;
      }
      // 根据Key查找索引
      const idx = this._find(key);
      // 根据索引查找值
      const val = this.storageContents[idx];
      // 找得到,设置缓存
      if (idx > -1) {
        this.cachedKey = key;
        this.cachedKeyIdx = idx;
      }
      return val;
    }

    /**
     * 返回Map对象上所有的值,返回类型为迭代器
     */
    values() {
      return GenerateIterator(
        this.storageKeys,
        this.storageContents,
        (key, value) => {
          return value;
        }
      );
    }

    /**
     * 传入回调函数,以遍历Map对象上的Key-Value
     */
    forEach(fn) {
      this.storageKeys.forEach((key, keyIdx) => {
        const val = this.storageContents[keyIdx];
        fn(key, val, this);
      });
    }

    /**
     * 返回Map的所有Key-Value,返回类型是一个迭代器,和Map自身的迭代器是同一个东西
     */
    entries() {
      return this[Symbol.iterator]();
    }

    /**
     * 返回Map的所有键,返回类型是一个迭代器
     */
    keys() {
      return GenerateIterator(
        this.storageKeys,
        this.storageContents,
        (key, value) => {
          return key;
        }
      );
    }

    _initCache() {
      this.cachedKey = SentinelKey;
      this.cachedKeyIdx = -1;
    }

    _find(key) {
      return this.storageKeys.findIndex((v) => {
        // 因为某个Symbol值只能在同一个变量持有它的情况下才能比较,而find方法内部无法完成这样的操作的
        return (Number.isNaN(v) && Number.isNaN(key)) || v === key;
      });
    }

    /**
     * 获取缓存,命中缓存返回 非负数,否则返回 -1
     * @param {any} key
     * @returns { number }
     */
    _getCache(key) {
      return (Number.isNaN(this.cachedKey) && Number.isNaN(key)) ||
        this.cachedKey === key
        ? this.cachedKeyIdx
        : -1;
    }

    clear() {
      this.storageKeys.length = 0;
      this.storageContents.length = 0;
      this._initCache();
    }
  }

  return new MyMapImplement(iterator);
}

# Set

如果实现了Map,那么实现Set就是手到擒来了。因为Set是一个特殊的Map,它的KeyValue是同一个。

之所以数组去重能够使用Set实现,其本质就是因为Map保证了的唯一性。

const set = new Set([1, 1, 2, 3, "1"]);
const res = [...set]; // 1,2,3,'1'

比如以下情况不能实现去重,因为不是用同一个变量引用的对象,就是三个对象,Map 就会创建多条记录,也就无法完成去重了。

const set = new Set([{}, {}, {}]);
const res = [...set]; // {},{},{}

以下是Set的实现:

import { MyMap } from "./Map";

class MySet {
  _map = new MyMap();

  get size() {
    return this._map.size;
  }

  [Symbol.iterator]() {
    return this._map.values();
  }

  constructor(iterator) {
    const arr = [...iterator];
    arr.length &&
      arr.forEach((val) => {
        this._map.set(val, val);
      });
  }

  keys() {
    return this._map.keys();
  }

  values() {
    return this._map.values();
  }

  entries() {
    return this._map.entries();
  }

  add(val) {
    return this._map.set(val, val);
  }

  delete(val) {
    return this._map.delete(val);
  }

  has(key) {
    return this._map.has(key);
  }

  forEach(fn) {
    this._map.forEach(fn);
  }

  clear() {
    this._map.clear();
  }
}
ON THIS PAGE