在这个答案的更新3中已经明确表示,这个符号:

var hash = {};
hash[X]

实际上并不散列对象X;它实际上只是将X转换为一个字符串(如果它是一个对象,则通过. tostring(),或者其他一些用于各种基本类型的内置转换),然后在“哈希”中查找该字符串,而不散列它。对象相等性也不检查-如果两个不同的对象具有相同的字符串转换,它们将相互覆盖。

鉴于此,JavaScript中是否有有效的hashmap实现?

(例如,javascript hashmap的第二个谷歌结果对任何操作产生的实现都是O(n)。其他各种结果忽略了具有等效字符串表示的不同对象会相互覆盖这一事实。


当前回答

下面是一种使用类似Java映射的简单而方便的方法:

var map = {
              'map_name_1': map_value_1,
              'map_name_2': map_value_2,
              'map_name_3': map_value_3,
              'map_name_4': map_value_4
          }

为了得到值:

alert(map['map_name_1']);    // Gives the value of map_value_1

... etc. ...

其他回答

现在有一些非常好的外部库解决方案:

collections.js Immutable.js

JavaScript也有其语言提供的Map。

地图

您可以使用ECMAScript 6 WeakMap或Map:

weakmap是键/值映射,其中键是对象。

映射对象是简单的键/值映射。任何值(包括对象和基本值)都可以用作键或值。

请注意,这两者都没有得到广泛支持,但您可以使用ECMAScript 6 Shim(需要本地ECMAScript 5或ECMAScript 5 Shim)来支持Map,但不能使用WeakMap(请参阅原因)。

如果性能不是很关键(例如,键的数量相对较小),并且你不想用额外的字段(如_hash, _id等)污染你的(或可能不是你的)对象,那么你可以利用Array.prototype.indexOf采用严格相等的事实。下面是一个简单的实现:

var Dict = (function(){
    // Internet Explorer 8 and earlier does not have any Array.prototype.indexOf
    function indexOfPolyfill(val) {
      for (var i = 0, l = this.length; i < l; ++i) {
        if (this[i] === val) {
          return i;
        }
      }
      return -1;
    }

    function Dict(){
      this.keys = [];
      this.values = [];
      if (!this.keys.indexOf) {
        this.keys.indexOf = indexOfPolyfill;
      }
    };

    Dict.prototype.has = function(key){
      return this.keys.indexOf(key) != -1;
    };

    Dict.prototype.get = function(key, defaultValue){
      var index = this.keys.indexOf(key);
      return index == -1 ? defaultValue : this.values[index];
    };

    Dict.prototype.set = function(key, value){
      var index = this.keys.indexOf(key);
      if (index == -1) {
        this.keys.push(key);
        this.values.push(value);
      } else {
        var prevValue = this.values[index];
        this.values[index] = value;
        return prevValue;
      }
    };

    Dict.prototype.delete = function(key){
      var index = this.keys.indexOf(key);
      if (index != -1) {
        this.keys.splice(index, 1);
        return this.values.splice(index, 1)[0];
      }
    };

    Dict.prototype.clear = function(){
      this.keys.splice(0, this.keys.length);
      this.values.splice(0, this.values.length);
    };

    return Dict;
})();

用法示例:

var a = {}, b = {},
    c = { toString: function(){ return '1'; } },
    d = 1, s = '1', u = undefined, n = null,
    dict = new Dict();

// Keys and values can be anything
dict.set(a, 'a');
dict.set(b, 'b');
dict.set(c, 'c');
dict.set(d, 'd');
dict.set(s, 's');
dict.set(u, 'u');
dict.set(n, 'n');

dict.get(a); // 'a'
dict.get(b); // 'b'
dict.get(s); // 's'
dict.get(u); // 'u'
dict.get(n); // 'n'
// etc.

与ECMAScript 6 WeakMap相比,它有两个问题:O(n)搜索时间和非弱(即如果不使用delete或clear释放键会导致内存泄漏)。

我已经实现了一个JavaScript HashMap,其代码可以从http://github.com/lambder/HashMapJS/tree/master获得

代码如下:

/*
 =====================================================================
 @license MIT
 @author Lambder
 @copyright 2009 Lambder.
 @end
 =====================================================================
 */
var HashMap = function() {
  this.initialize();
}

HashMap.prototype = {
  hashkey_prefix: "<#HashMapHashkeyPerfix>",
  hashcode_field: "<#HashMapHashkeyPerfix>",

  initialize: function() {
    this.backing_hash = {};
    this.code = 0;
  },
  /*
   Maps value to key returning previous association
   */
  put: function(key, value) {
    var prev;
    if (key && value) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        prev = this.backing_hash[hashCode];
      } else {
        this.code += 1;
        hashCode = this.hashkey_prefix + this.code;
        key[this.hashcode_field] = hashCode;
      }
      this.backing_hash[hashCode] = value;
    }
    return prev;
  },
  /*
   Returns value associated with given key
   */
  get: function(key) {
    var value;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        value = this.backing_hash[hashCode];
      }
    }
    return value;
  },
  /*
   Deletes association by given key.
   Returns true if the association existed, false otherwise
   */
  del: function(key) {
    var success = false;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        var prev = this.backing_hash[hashCode];
        this.backing_hash[hashCode] = undefined;
        if(prev !== undefined)
          success = true;
      }
    }
    return success;
  }
}

//// Usage

// Creation

var my_map = new HashMap();

// Insertion

var a_key = {};
var a_value = {struct: "structA"};
var b_key = {};
var b_value = {struct: "structB"};
var c_key = {};
var c_value = {struct: "structC"};

my_map.put(a_key, a_value);
my_map.put(b_key, b_value);
var prev_b = my_map.put(b_key, c_value);

// Retrieval

if(my_map.get(a_key) !== a_value){
  throw("fail1")
}
if(my_map.get(b_key) !== c_value){
  throw("fail2")
}
if(prev_b !== b_value){
  throw("fail3")
}

// Deletion

var a_existed = my_map.del(a_key);
var c_existed = my_map.del(c_key);
var a2_existed = my_map.del(a_key);

if(a_existed !== true){
  throw("fail4")
}
if(c_existed !== false){
  throw("fail5")
}
if(a2_existed !== false){
  throw("fail6")
}

你必须在某些内部状态下存储对象/值对:

HashMap = function(){
  this._dict = [];
}

HashMap.prototype._get = function(key){
  for(var i=0, couplet; couplet = this._dict[i]; i++){
    if(couplet[0] === key){
      return couplet;
    }
  }
}

HashMap.prototype.put = function(key, value){
  var couplet = this._get(key);
  if(couplet){
    couplet[1] = value;
  }else{
    this._dict.push([key, value]);
  }
  return this; // for chaining
}
HashMap.prototype.get = function(key){
  var couplet = this._get(key);
  if(couplet){
    return couplet[1];
  }
}

并这样使用它:

var color = {}; // Unique object instance
var shape = {}; // Unique object instance
var map = new HashMap();
map.put(color, "blue");
map.put(shape, "round");
console.log("Item is", map.get(color), "and", map.get(shape));

当然,这个实现也类似于O(n)。Eugene的例子是获得能够以实际哈希所期望的任何速度工作的哈希的唯一方法。

另一种方法,沿着Eugene的答案,是以某种方式为所有对象附加一个唯一的ID。我最喜欢的方法之一是从Object超类继承的内置方法之一,将其替换为自定义函数传递,并将属性附加到该函数对象。如果你重写我的HashMap方法来做到这一点,它看起来像:

HashMap = function(){
  this._dict = {};
}

HashMap.prototype._shared = {id: 1};
HashMap.prototype.put = function put(key, value){
  if(typeof key == "object"){
    if(!key.hasOwnProperty._id){
      key.hasOwnProperty = function(key){
        return Object.prototype.hasOwnProperty.call(this, key);
      }
      key.hasOwnProperty._id = this._shared.id++;
    }
    this._dict[key.hasOwnProperty._id] = value;
  }else{
    this._dict[key] = value;
  }
  return this; // for chaining
}

HashMap.prototype.get = function get(key){
  if(typeof key == "object"){
    return this._dict[key.hasOwnProperty._id];
  }
  return this._dict[key];
}

这个版本看起来只是稍微快一点,但在理论上,对于大数据集来说,它会快得多。