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。在此以前,甚至以後,你應該使用空對象知足你全部的基本哈希映射需求。

歡迎光臨個人我的博客:風影博客

參考

相關文章
相關標籤/搜索