JavaScript中真正的哈希映射(译)

在JavaScript中存储键值对的一个简单常见的方法是使用对象字面量。然而,对象字面量不是真正意义上的哈希映射,若是使用不当可能会构成潜在的隐患。虽然目前JavaScript可能没有提供原生的hashmap(至少不能跨浏览器),对象字面量若是没有隐患就能达到所需的功能也许是一个更好的选择。javascript

对象字面量存在的问题

对象字面的问题在于其原型链继承自Object原型上的对象和方法会破坏其维持键值的机制。以toString方法为例,使用in操做符检查同名属性会致使错误的结果:java

javascriptvar map = {};
`toString` in map; // true

上面的错误之因此会发生,是由于in操做符会从对象的原型链上查找继承属性。为了解决该问题,咱们能够用hasOwnProperty方法来肯定键值的存在性,由于该方法只检查对象自己的属性:浏览器

javascriptvar map = {};
map.hasOwnProperty('toString'); // false

上面的方法可以良好的工做,除非你遇到一个名为hasOwnProperty键。重写此方法将会由于尝试调用hasOwnProperty方法而致使意外的行为,根据新的值最有可能致使错误:数据结构

javascriptvar map = {};
map.hasOwnProperty = 'foo';
map.hasOwnProperty('hasOwnProperty'); // TypeError

一个快速的修正方法是利用一个通用且没有被篡改的对象字面量,并在你指定的hashmap上下文中执行hasOwnProperty方法:prototype

javascriptvar map = {};
map.hasOwnProperty = 'foo';
{}.hasOwnProperty.call(map, 'hasOwnProperty'); // true

尽管实际工做时没有任何问题,但对象字面量仍是限制了它的使用。举个例子,每次你在for...in循环里面遍历一个对象的属性,你都要过滤其原型链中的属性:code

javascriptvar map = {};
var has = {}.hasOwnProperty;

for (var key in map) {
    if(has.call(map, key)) {
        // ...
    }
}

一段时间后,可能会变得有点乏味。值得庆幸的是,有一个更好的办法。对象

空对象

建立一个真正的哈希映射的秘诀就是避免原型,及其带来的包袱。咱们能够利用ES5中引入的Object.create方法达到该目的。该方法的特别之处在于你能够给一个新对象明肯定义原型。举个例子,用一个较复杂的方式定义一个简单对象字面量:继承

javascriptvar obj = {};
// 等价于
var obj = Object.create(Object.prototype);

除了可以定义一个你选择的原型,你也可以经过传入一个null放弃传入原型:ip

javascriptvar map = Object.create(null);

map instanceof Object; // false
Object.prototype.isPrototypeOf(map); // false
Object.getPrototypeOf(map); // null

这些空对象对于哈希映射是理想的,由于缺乏[[Prototype]]避免了命名冲突。因为该对象彻底是空的,它会抵制任何形式的强制转换,试图这样作将致使一个错误:原型链

javascriptvar map = Object.create(null);
map + ""; // TypeError: Cannot convert object to primitive value

空对象没有任何初始值或者字符串表现形式,由于空对象除了做为键值对的存储空间没有为任何其余事情作打算,简单又普通。

注意hasOwnProperty方法在空对象中也消失了,这可有可无,由于in操做符能够无异常的工做了:

javascriptvar map = Object.create(null);
'toString' in map; // false

更好的是,乏味的for...in循环变得更加简单。咱们最终能够按其自己的意思写一个循环:

javascriptvar map = Object.create(null);
for (var key in map) {
    // ...
}

虽然存在差别,但对全部的意图和目的,它仍然表现得就像一个对象字面量。属性能够利用.或则[]访问,对象能够被序列化,且对象仍然可使用上下文对象的原型方法:

javascriptvar map = Object.create(null);

Object.defineProperties(map, {
    'foo': {
        value: 1,
        enumerable: true
    },
    'bar': {
        value: 2,
        enumerable: false
    }
});

map.foo; // 1
map['bar']; // 2

JSON.stringify(map); // {"foo": 1}

{}.hasOwnProperty.call(map, 'foo'); // true
{}.propertyIsEnumerable.call(map, 'bar'); // false

甚至不一样检查类型的方法将会告诉你从对象字面中指望获得什么:

javascriptvar map = Object.create(null);
typeof map; // object
{}.toString.call(map); // [object Object]
{}.valueOf.call(map); // Object {}

这一切使得空对象代替对象字面量变得简单,让他们很好地集成到一个现有的应用程序,而不会引发大范围的变化。

总结

在简单的键值存储的背景下,使用空对象是对象字面量的有效替代方案,用明确的定义消除对象字面量的怪癖。对于更全面的数据结构,ES6将以MapSet形式引入原生的hashmap。在此以前,甚至以后,你应该使用空对象知足你全部的基本哈希映射需求。

欢迎光临个人我的博客:风影博客

参考

相关文章
相关标签/搜索