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

在JavaScript中存储键值对的一个简朴罕见的要领是运用对象字面量。但是,对象字面量不是真正意义上的哈希映照,假如运用不当能够会组成潜伏的隐患。虽然现在JavaScript能够没有供应原生的hashmap(最少不能跨浏览器),对象字面量假如没有隐患就能够到达所需的功用也许是一个更好的挑选。

对象字面量存在的题目

对象字面的题目在于其原型链继续自Object原型上的对象和要领会损坏其保持键值的机制。以toString要领为例,运用in操作符搜检同名属性会致使毛病的效果:

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要领:

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

只管实际事变时没有任何题目,但对象字面量照样限定了它的运用。举个例子,每次你在for...in轮回内里遍历一个对象的属性,你都要过滤其原型链中的属性:

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摒弃传入原型:

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。在此之前,以至以后,你应当运用空对象满足你一切的基础哈希映照需求。

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

参考

    原文作者:凡影
    原文地址: https://segmentfault.com/a/1190000002799624
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞