手写代码之实现ES6的Set和Map

1,510 阅读4分钟

熟悉JavaScript的都知道,ES6中新增了两种新的数据结构 SetMap

这里不介绍这两种数据结构的用法,如果不知道怎么使用这两种数据结构。可以看这里

俗话说,知其然并知其所以然,我们虽然知道这两种数据结构怎么使用,但我们最好能知道它们是怎么实现的。

所以我们就来实现简易版的Set和Map。

Set

Set数据结构也称作集合;类似于数组,但是成员的值都是唯一的,没有重复的值。

我们知道普通的JavaScript对象是{键: 值}对的形式;所以有人觉得也可以将Set数据结构理解成存储{值: 值}对的对象,因为对象是不允许有相同的键。

所以实现Set数据结构也有两种方式:数组或对象。本文采用的是后面一种。

接下来,需要声明一些Set可用的属性和方法(尝试模拟与ES6实现相同的Set类)。

属性:

  • size:返回Set所包含元素的数量。

方法:

  • has(element):如果元素在Set中,返回true,否则返回false。
  • add(element):向Set添加一个新元素。
  • delete(element):从Set移除一个元素。
  • clear():移除Set中的所有元素。
  • values():返回一个包含Set中所有值的数组。

直接上代码:

class Set {
  constructor() {
    this.items = {};
    this.size = 0;
  }

  has(element) {
    return element in this.items;
  }

  add(element) {
    if(! this.has(element)) {
      this.items[element] = element;
      this.size++;
    }
    return this;
  }

  delete(element) {
    if (this.has(element)) {
      delete this.items[element];
      this.size--;
    }
    return this;
  }

  clear() {
    this.items = {}
    this.size = 0;
  }

  values() {
    let values = [];
    for(let key in this.items) {
      if(this.items.hasOwnProperty(key)) {
        values.push(key);
      }
    }
    return values;
  }
}

上面简易版的Set只是实现了ES6中Set数据结构的常用方法,Set数据结构中keys方法与values方法完全相同,所以也没有实现。entries、forEach等方法也没有实现。

Map

Map数据结构也称作字典或映射;类似于对象,也是键值对的集合;但普通的Javascript对象(Objec)只能用字符串当作键。这给它的使用带来了很大的限制。

而Map数据结构“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

类似于Set,声明一些Set可用的属性和方法

属性:

  • size:返回Map所包含元素的数量。

方法:

  • has(key):如果元素在Map中,返回true,否则返回false。
  • set(key, value):向Map添加一个新元素。
  • delete(key):从Map移除一个元素。
  • get(key):从Map查找一个元素。
  • clear():移除Map中的所有元素。
  • keys():返回一个包含Map中所有键的数组。
  • values():返回一个包含Map中所有值的数组。
function defaultToString(key) {
  if(key === null) {
    return 'NULL';
  } else if (key === undefined) {
    return 'UNDEFINED'
  } else if (Object.prototype.toString.call(key) === '[object Object]' || Object.prototype.toString.call(key) === '[object Array]') {
    return JSON.stringify(key);
  }
  return key.toString();
}

class Map {
  constructor() {
    this.items = {};
    this.size = 0;
  }

  set(key, value) {
    if(!this.has(key)) {
      this.items[defaultToString(key)] = value;
      this.size++;
    }
    return this;
  }

  get(key) {
    return this.items[defaultToString(key)];
  }

  has(key) {
    return this.items[defaultToString(key)] !== undefined;
  }

  delete(key) {
    if (this.has(key)) {
      delete this.items[key];
      this.size--;
    }
    return this;
  }

  clear() {
    this.items = {}
    this.size = 0;
  }

  keys() {
    let keys = [];
    for(let key in this.items) {
      if(this.has(key)) {
        keys.push(key)
      }
    }
    return keys;
  }

  values() {
    let values = [];
    for(let key in this.items) {
      if(this.has(key)) {
        values.push(this.items[key]);
      }
    }
    return values;
  }
}

因为Map数据结构的键可以是任意值,所以我们需要先实现一个将其他值转换成字符串的方法(defaultToString)

同上面Set一样;上面简易版的Map只是实现了ES6中Map数据结构的常用方法;entries、forEach等方法也没有实现。

ES6中的WeakMap和WeakSet

除了Set和Map这两种新的数据结构,ES6还增加了它们的弱化版本,WeakSet和WeakMap。

基本上,Map和Set与其弱化版本之间仅有的区别是:

  • WeakSet或WeakMap类没有keys和values等遍历方法;
  • 只能用对象作为键;且是弱引用对象。 在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 和 WeakSet 是不能被遍历的。

小结

其实知道原理后,自己动手实现一个简易的Set或Map也不难;感兴趣的小伙伴可以自己动手实现一下。