还在JavaScript中把对象当哈希表用吗?

352 阅读2分钟

哈希表是日常编程中最常用的数据结构之一。它保存可以通过其 轻松访问 的键值对。在Java中很显然可以使用 HashMap。在JavaScript中为了实现哈希表使用 Object 也非常方便。

// insert key-value-pair
map['key1'] = 'value1';
map['key2'] = 'value2';
map['key3'] = 'value3';

// check if map contians key
if (map['key1']) {
  console.log('Map contains key1');
}

// get value with specific key
console.log(map['key1']);

但是JavaScript中有一个专门用于此目的的内置数据结构:Map。并且,Map结构相比Object有以下几个优势:

更多的键值类型

相比于对象的键值只能有 SymbolString 类型。Map可以将任何类型的值作为键:对象,函数或基础类型。

const map = new Map();
const myFunction = () => console.log('I am a useful function.');
const myNumber = 666;
const myObject = {
  name: 'plainObjectValue',
  otherKey: 'otherValue'
};
map.set(myFunction, 'function as a key');
map.set(myNumber, 'number as a key');
map.set(myObject, 'object as a key');

console.log(map.get(myFunction)); // function as a key
console.log(map.get(myNumber)); // number as a key
console.log(map.get(myObject)); // object as a key

更方便确定大小

Map内部提供了 size 属性,可以方便获得map实例的大小。获取对象的大小却要绕一些远路。

const map = new Map();
map.set('someKey1', 1);
map.set('someKey2', 1);
...
map.set('someKey100', 1);

console.log(map.size) // 100, Runtime: O(1)

const plainObjMap = {};
plainObjMap['someKey1'] = 1;
plainObjMap['someKey2'] = 1;
...
plainObjMap['someKey100'] = 1;

console.log(Object.keys(plainObjMap).length) // 100, Runtime: O(n)

更优越的性能

基于上面的原因,在需要获取数据量大小的场景,Map拥有更好的性能,可以在常数时间获取map的大小,而对象却需要O(n)的时间。以使用Macbook Pro的示例为例,两种数据结构对一千万个条目确定大小的平均持续时间:

  • 普通JS对象:1.6s
  • Map: 1ms

更直接的迭代

必须通过获取密钥并对其进行迭代来迭代对象,但是Map可以直接进行迭代。

const map = new Map();
map.set('someKey1', 1);
map.set('someKey2', 2);
map.set('someKey3', 3);

for (let [key, value] of map) {
  console.log(`${key} = ${value}`);
}
// someKey1 = 1
// someKey2 = 2
// someKey3 = 3

const plainObjMap = {};
plainObjMap['someKey1'] = 1;
plainObjMap['someKey2'] = 2;
plainObjMap['someKey3'] = 3;

for (let key of Object.keys(plainObjMap)) {
  const value = plainObjMap[key];
  console.log(`${key} = ${value}`);
}
// someKey1 = 1
// someKey2 = 2
// someKey3 = 3

键值有序

在ECMAScript 2015之前,不保证对象的键具有任何特定顺序。在Map上进行迭代可确保键按插入顺序出现。

无键值冲突

普通对象由于其原型已经包含一些属性。插入的键值和对象已包含的键之间可能存在冲突。Map在创建时不包含任何键。

注意:从ECMAScript 2015开始,可以通过使用Object.create(null)创建纯对象来避免意外的键覆盖。

const map = new Map();
map.set('someKey1', 1);
map.set('someKey2', 2);
map.set('toString', 3); // No problem for Map
console.log(map.get("toString"));
const plainObjMap = new Map();
plainObjMap['someKey1'] = 1;
plainObjMap['someKey2'] = 2;
plainObjMap['toString'] = 3; 
console.log(plainObjMap.toString());  //TypeError: plainObjMap.toString is not a function