如何在JavaScript中进行关联数组/哈希


574

我需要使用JavaScript来存储一些统计信息,就像在C#中那样:

Dictionary<string, int> statistics;

statistics["Foo"] = 10;
statistics["Goo"] = statistics["Goo"] + 1;
statistics.Add("Zoo", 1);

JavaScript中是否有Hashtable类似的东西Dictionary<TKey, TValue>
如何以这种方式存储值?


1
js是松散类型的,因此无法只声明一个字符串或整数,只能声明一个var并为其分配一个字符串或整数。:D
Gordon Gustafson,2009年

您可能要签出xDict。jsfiddle.net/very/MuVwd这是字典String =>用Java语言编写的任何东西。
罗伯特


接受的答案写于2009年-仅支持字符串键。对于非字符串键,请使用Map或WeakMap,如Vitalii的answer所示
制造商

Answers:


564

使用JavaScript对象作为关联数组

关联数组:简单来说,关联数组使用String而不是Integer数字作为索引。

创建一个对象

var dictionary = {};

Javascript允许您使用以下语法向对象添加属性:

Object.yourProperty = value;

相同的替代语法是:

Object["yourProperty"] = value;

如果您还可以使用以下语法创建键值对象映射

var point = { x:3, y:2 };

point["x"] // returns 3
point.y // returns 2

您可以使用for..in循环构造遍历关联数组,如下所示

for(var key in Object.keys(dict)){
  var value = dict[key];
  /* use key/value for intended purpose */
}

36
请注意,作者使用初始化“关联数组”的方法已new Array()被皱眉。该文章最终提到了它的缺点,并提出了建议new Object(){}作为首选的替代方法,但这已经接近尾声了,我担心大多数读者都不会这么想。
丹尼尔·卢巴罗夫

23
失败。JavaScript不支持将对象引用作为键,而像Flash / AS3 Dictionary这样的对象则支持。在JavaScript中var obj1 = {}; var obj2 = {}; var table= {}; table[obj1] = "A"; table[obj2] = "B"; alert(table[obj1]); //displays B,因为无法区分键obj1和obj2;它们都被转换为字符串,并且变成了类似“对象”之类的东西。完全失败,并且使带有引用和循环引用的类型安全的序列化在JavaScript中完整困难或不起作用。在Flash / AS3中很容易。
Triynko

好吧,在JS中,我们可以通过检查相等性或通过以下方法定义equals方法来验证的唯一方法: Point.prototype.equals = function(obj) { return (obj instanceof Point) && (obj.x === this.x) && (obj.y === this.y); };
Nadeem

1
@Leo的console.log({A: 'B',C: 'd'} [富])应该给你甲B.
ychaouche

2
@Leo该示例似乎是错误的。for... in因为字典会在其键上循环播放,所以Object.keys似乎放错了地方。Object.keys返回字典的键的数组,for... in对于一个数组,循环遍历 “键”,对于“数组”,数组是其索引,而不是其值。
JHH

434
var associativeArray = {};
associativeArray["one"] = "First";
associativeArray["two"] = "Second";
associativeArray["three"] = "Third";

如果您来自面向对象的语言,则应查阅本文


38
您也可以用更少的行来完成此操作:var associativeArray = {“ one”:“ First”,“ two”:“ second”,“ three”:“ Third”}; 然后associativeArray [“ one”]返回“ First”,而assocativeArray [“ four”]返回null。
托尼·威克姆

2
抱歉@JuusoOhtonen,我在6年前写了这篇文章(真是太快了!)我已经更新了链接。请检查它,不要犹豫,问是否有任何疑问
Dani Cricco

145

所有现代浏览器都支持javascript Map对象。有两个原因使使用Map比使用Object更好:

  • 对象具有原型,因此地图中包含默认键。
  • 对象的键是字符串,其中键可以是Map的任何值。
  • 当您必须跟踪对象的大小时,可以轻松获取地图的大小。

例:

var myMap = new Map();

var keyObj = {},
    keyFunc = function () {},
    keyString = "a string";

myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");

myMap.size; // 3

myMap.get(keyString);    // "value associated with 'a string'"
myMap.get(keyObj);       // "value associated with keyObj"
myMap.get(keyFunc);      // "value associated with keyFunc"

如果要垃圾回收未从其他对象引用的键,请考虑使用WeakMap而不是Map。


5
希望在几年内,这将是投票最多的答案。
金马伦·李

1
@CameronLee一定会的–LoïcFaure
-Lacroix

1
Map当您的键是一个对象但应按值而不是引用进行比较时,这几乎没有用。
思源仁

7
在写完这个答案一年多之后,“所有现代浏览器都支持Map”仍然不是事实。只有在台式机上,您才能至少依靠基本的Map支持。不在移动设备上。例如,Android浏览器根本不支持Map。即使在桌面上,某些实现也不完整。例如,IE11仍然不支持通过“ for ... of ...”进行枚举,因此,如果要与IE兼容,则必须使用令人讨厌的.forEach混合。另外,JSON.stringify()在我尝试使用的任何浏览器中都不适用于Map。另外,初始化程序在IE或Safari中不起作用。
戴夫·伯顿

3
有出色的浏览器支持。再检查一遍。无论如何,这很容易实现polyfill,因此本机浏览器支持不是问题。
布拉德,

132

除非有特殊原因,否则请使用普通对象。可以使用哈希表样式语法引用Javascript中的对象属性:

var hashtable = {};
hashtable.foo = "bar";
hashtable['bar'] = "foo";

两个foobar元素,现在则引用如下:

hashtable['foo'];
hashtable['bar'];
// or
hashtable.foo;
hashtable.bar;

当然,这确实意味着您的密钥必须是字符串。如果它们不是字符串,则会在内部将其转换为字符串,因此它可能仍然有效,YMMV。


1
键作为整数使我没有问题。stackoverflow.com/questions/2380019/...
乔纳斯Elfström

10
乔纳斯:请记住,在设置属性时,您的整数将转换为字符串:var hash = {}; hash[1] = "foo"; alert(hash["1"]);警报“ foo”。
Tim Down'7

17
如果您的密钥之一是“ proto ”或“ parent ”怎么办?
2011年

5
请注意,对象不能用作 JavaScript中的。可以,但是可以将它们转换为String表示形式,因此任何Object都将以完全相同的键结尾。请参阅下面的@TimDown的jshashtable建议。
ericsoco

21
此示例令人困惑,因为您在两个实例中同时使用了foo和bar作为键和值。更清楚地表明var dict = {}; dict.key1 = "val1"; dict["key2"] = "val2";dict的key1元素可以同时由dict["key1"]和引用dict.key1
2014年

49

由于JS中的每个对象的行为类似-通常实现为-哈希表,因此我就去做...

var hashSweetHashTable = {};

26
拒绝投票,因为它没有显示如何实际访问“哈希表”中的值。
IQAndreas

我迟到了9年(我对编程一无所知,更不用说这个网站了),但是...如果您试图将点存储在地图上并且需要查看是否已经有东西该怎么办在地图上的某个点?在那种情况下,最好使用HashTable,按坐标(一个对象,而不是一个string)查找。
Mike Warren

@MikeWarren if (hashSweetHashTable.foo)应该输入if块if foo设置。
Koray Tugay

21

因此在C#中,代码如下所示:

Dictionary<string,int> dictionary = new Dictionary<string,int>();
dictionary.add("sample1", 1);
dictionary.add("sample2", 2);

要么

var dictionary = new Dictionary<string, int> {
    {"sample1", 1},
    {"sample2", 2}
};

在JavaScript中

var dictionary = {
    "sample1": 1,
    "sample2": 2
}

C#字典对象包含有用的方法,例如dictionary.ContainsKey() 在JavaScript中,我们可以使用hasOwnPropertylike

if (dictionary.hasOwnProperty("sample1"))
    console.log("sample1 key found and its value is"+ dictionary["sample1"]);

1
对我而言,不必写下答案hasOwnProperty
-Brichins

18

如果您要求键必须是任何对象而不仅仅是字符串,那么可以使用我的jshashtable


3
在发现这一点之前,我花了几个小时来解决以下事实:Objects不能真正用作JS-style-Object-as-associative-arrays的键?谢谢你,蒂姆。
ericsoco

1
Flash / AS3词典以及大多数其他语言都支持将对象引用用作键。JavaScript仍未实现,但我认为它在将来的规范中属于某种Map类。在此期间再次使用polyfills;标准太多了。哦,等等...终于在2015年,地图似乎已经到了:stackoverflow.com/a/30088129/88409,并且受到“现代”浏览器的支持,大声笑:kangax.github.io/compat-table/es6/#地图(并没有得到广泛支持)。仅比AS3落后十年。
Triynko

蒂姆,也许您应该更新jshashtable以在可用的地方使用Map()。
戴夫·伯顿

1
@DaveBurton:好计划。我一有时间就会这样做。
Tim Down

6
function HashTable() {
    this.length = 0;
    this.items = new Array();
    for (var i = 0; i < arguments.length; i += 2) {
        if (typeof (arguments[i + 1]) != 'undefined') {
            this.items[arguments[i]] = arguments[i + 1];
            this.length++;
        }
    }

    this.removeItem = function (in_key) {
        var tmp_previous;
        if (typeof (this.items[in_key]) != 'undefined') {
            this.length--;
            var tmp_previous = this.items[in_key];
            delete this.items[in_key];
        }

        return tmp_previous;
    }

    this.getItem = function (in_key) {
        return this.items[in_key];
    }

    this.setItem = function (in_key, in_value) {
        var tmp_previous;
        if (typeof (in_value) != 'undefined') {
            if (typeof (this.items[in_key]) == 'undefined') {
                this.length++;
            } else {
                tmp_previous = this.items[in_key];
            }

            this.items[in_key] = in_value;
        }

        return tmp_previous;
    }

    this.hasItem = function (in_key) {
        return typeof (this.items[in_key]) != 'undefined';
    }

    this.clear = function () {
        for (var i in this.items) {
            delete this.items[i];
        }

        this.length = 0;
    }
}

1
对于不赞成这一点的人,您能发表评论吗?该答案发布于2011年,而不是当前日期。
2015年

2
我没有拒绝投票,但是...您不应该将数组用作对象。不能100%确定这是否是您的意图。在不删除的数组上使用切片来重新索引;删除是可以的,但是将其设置为undefined-最好是明确的;use = undefined在对象上也是b / c更快(但更多内存)。简而言之:始终使用对象:{}不是数组:[]或者new Array()如果您打算使用字符串键,否则js引擎会出现问题-对于1个变量,它将看到2种类型,这意味着没有优化,或者它将与数组一起运行并实现它必须更改为对象(可能的重新分配)。
Graeme Wicksted

2
就像Alex Hawkins的答案一样,请提供一些解释,为什么这个看起来很复杂的代码实际上有用并且比这里给出的其他简短答案更好。
Thomas Tempelmann '16

6

我创建此文件是为了解决一些问题,例如对象键映射,枚举(带有forEach()方法)和清除功能。

function Hashtable() {
    this._map = new Map();
    this._indexes = new Map();
    this._keys = [];
    this._values = [];
    this.put = function(key, value) {
        var newKey = !this.containsKey(key);
        this._map.set(key, value);
        if (newKey) {
            this._indexes.set(key, this.length);
            this._keys.push(key);
            this._values.push(value);
        }
    };
    this.remove = function(key) {
        if (!this.containsKey(key))
            return;
        this._map.delete(key);
        var index = this._indexes.get(key);
        this._indexes.delete(key);
        this._keys.splice(index, 1);
        this._values.splice(index, 1);
    };
    this.indexOfKey = function(key) {
        return this._indexes.get(key);
    };
    this.indexOfValue = function(value) {
        return this._values.indexOf(value) != -1;
    };
    this.get = function(key) {
        return this._map.get(key);
    };
    this.entryAt = function(index) {
        var item = {};
        Object.defineProperty(item, "key", {
            value: this.keys[index],
            writable: false
        });
        Object.defineProperty(item, "value", {
            value: this.values[index],
            writable: false
        });
        return item;
    };
    this.clear = function() {
        var length = this.length;
        for (var i = 0; i < length; i++) {
            var key = this.keys[i];
            this._map.delete(key);
            this._indexes.delete(key);
        }
        this._keys.splice(0, length);
    };
    this.containsKey = function(key) {
        return this._map.has(key);
    };
    this.containsValue = function(value) {
        return this._values.indexOf(value) != -1;
    };
    this.forEach = function(iterator) {
        for (var i = 0; i < this.length; i++)
            iterator(this.keys[i], this.values[i], i);
    };
    Object.defineProperty(this, "length", {
        get: function() {
            return this._keys.length;
        }
    });
    Object.defineProperty(this, "keys", {
        get: function() {
            return this._keys;
        }
    });
    Object.defineProperty(this, "values", {
        get: function() {
            return this._values;
        }
    });
    Object.defineProperty(this, "entries", {
        get: function() {
            var entries = new Array(this.length);
            for (var i = 0; i < entries.length; i++)
                entries[i] = this.entryAt(i);
            return entries;
        }
    });
}


课程文件 Hashtable

方法:

  • get(key)
    返回与指定键关联的值。
    参数::
    key从中检索值的键。

  • put(key, value)
    将指定的值与指定的键关联。
    参数::
    key与值关联的键。
    value:与键关联的值。

  • remove(key)
    删除指定键及其值。
    参数::
    key要删除的密钥。

  • clear()
    清除所有哈希表,同时删除键和值。

  • indexOfKey(key)
    根据添加顺序返回指定键的索引。
    参数::
    key获取索引的键。

  • indexOfValue(value)
    根据添加顺序返回指定值的索引。
    参数::
    value其值获取索引。
    注意:
    此信息是通过indexOf()数组的方法检索的,因此它仅将对象与toString()方法进行比较。

  • entryAt(index)
    返回具有两个属性的对象:键和值,表示指定索引处的条目。
    参数::
    index要获取的条目的索引。

  • containsKey(key)
    返回哈希表是否包含指定的键。
    参数::
    key要检查的键。

  • containsValue(value)
    返回哈希表是否包含指定值。
    参数::
    value要检查的值。

  • forEach(iterator)
    迭代指定中的所有条目iterator
    参数:
    value::用3个参数的方法keyvalueindex,其中,index表示所述条目的索引。

    特性:

  • length (只读
    获取哈希表中条目的计数。

  • keys (只读
    获取哈希表中所有键的数组。

  • values (只读
    获取哈希表中所有值的数组。

  • entries (只读
    获取哈希表中所有条目的数组。它们以与方法相同的形式表示entryAt()


2

https://gist.github.com/alexhawkins/f6329420f40e5cafa0a4

var HashTable = function() {
  this._storage = [];
  this._count = 0;
  this._limit = 8;
}


HashTable.prototype.insert = function(key, value) {
  //create an index for our storage location by passing it through our hashing function
  var index = this.hashFunc(key, this._limit);
  //retrieve the bucket at this particular index in our storage, if one exists
  //[[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ]  [ [k,v] ] ]
  var bucket = this._storage[index]
    //does a bucket exist or do we get undefined when trying to retrieve said index?
  if (!bucket) {
    //create the bucket
    var bucket = [];
    //insert the bucket into our hashTable
    this._storage[index] = bucket;
  }

  var override = false;
  //now iterate through our bucket to see if there are any conflicting
  //key value pairs within our bucket. If there are any, override them.
  for (var i = 0; i < bucket.length; i++) {
    var tuple = bucket[i];
    if (tuple[0] === key) {
      //overide value stored at this key
      tuple[1] = value;
      override = true;
    }
  }

  if (!override) {
    //create a new tuple in our bucket
    //note that this could either be the new empty bucket we created above
    //or a bucket with other tupules with keys that are different than 
    //the key of the tuple we are inserting. These tupules are in the same
    //bucket because their keys all equate to the same numeric index when
    //passing through our hash function.
    bucket.push([key, value]);
    this._count++
      //now that we've added our new key/val pair to our storage
      //let's check to see if we need to resize our storage
      if (this._count > this._limit * 0.75) {
        this.resize(this._limit * 2);
      }
  }
  return this;
};


HashTable.prototype.remove = function(key) {
  var index = this.hashFunc(key, this._limit);
  var bucket = this._storage[index];
  if (!bucket) {
    return null;
  }
  //iterate over the bucket
  for (var i = 0; i < bucket.length; i++) {
    var tuple = bucket[i];
    //check to see if key is inside bucket
    if (tuple[0] === key) {
      //if it is, get rid of this tuple
      bucket.splice(i, 1);
      this._count--;
      if (this._count < this._limit * 0.25) {
        this._resize(this._limit / 2);
      }
      return tuple[1];
    }
  }
};



HashTable.prototype.retrieve = function(key) {
  var index = this.hashFunc(key, this._limit);
  var bucket = this._storage[index];

  if (!bucket) {
    return null;
  }

  for (var i = 0; i < bucket.length; i++) {
    var tuple = bucket[i];
    if (tuple[0] === key) {
      return tuple[1];
    }
  }

  return null;
};


HashTable.prototype.hashFunc = function(str, max) {
  var hash = 0;
  for (var i = 0; i < str.length; i++) {
    var letter = str[i];
    hash = (hash << 5) + letter.charCodeAt(0);
    hash = (hash & hash) % max;
  }
  return hash;
};


HashTable.prototype.resize = function(newLimit) {
  var oldStorage = this._storage;

  this._limit = newLimit;
  this._count = 0;
  this._storage = [];

  oldStorage.forEach(function(bucket) {
    if (!bucket) {
      return;
    }
    for (var i = 0; i < bucket.length; i++) {
      var tuple = bucket[i];
      this.insert(tuple[0], tuple[1]);
    }
  }.bind(this));
};


HashTable.prototype.retrieveAll = function() {
  console.log(this._storage);
  //console.log(this._limit);
};

/******************************TESTS*******************************/

var hashT = new HashTable();

hashT.insert('Alex Hawkins', '510-599-1930');
//hashT.retrieve();
//[ , , , [ [ 'Alex Hawkins', '510-599-1930' ] ] ]
hashT.insert('Boo Radley', '520-589-1970');
//hashT.retrieve();
//[ , [ [ 'Boo Radley', '520-589-1970' ] ], , [ [ 'Alex Hawkins', '510-599-1930' ] ] ]
hashT.insert('Vance Carter', '120-589-1970').insert('Rick Mires', '520-589-1970').insert('Tom Bradey', '520-589-1970').insert('Biff Tanin', '520-589-1970');
//hashT.retrieveAll();
/* 
[ ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Tom Bradey', '520-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Rick Mires', '520-589-1970' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '520-589-1970' ] ] ]
*/

//overide example (Phone Number Change)
//
hashT.insert('Rick Mires', '650-589-1970').insert('Tom Bradey', '818-589-1970').insert('Biff Tanin', '987-589-1970');
//hashT.retrieveAll();

/* 
[ ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Tom Bradey', '818-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Rick Mires', '650-589-1970' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]

*/

hashT.remove('Rick Mires');
hashT.remove('Tom Bradey');
//hashT.retrieveAll();

/* 
[ ,
  [ [ 'Boo Radley', '520-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]


*/

hashT.insert('Dick Mires', '650-589-1970').insert('Lam James', '818-589-1970').insert('Ricky Ticky Tavi', '987-589-1970');
hashT.retrieveAll();


/* NOTICE HOW HASH TABLE HAS NOW DOUBLED IN SIZE UPON REACHING 75% CAPACITY ie 6/8. It is now size 16.
 [,
  ,
  [ [ 'Vance Carter', '120-589-1970' ] ],
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Dick Mires', '650-589-1970' ],
    [ 'Lam James', '818-589-1970' ] ],
  ,
  ,
  ,
  ,
  ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Ricky Ticky Tavi', '987-589-1970' ] ],
  ,
  ,
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]




*/
console.log(hashT.retrieve('Lam James'));  //818-589-1970
console.log(hashT.retrieve('Dick Mires')); //650-589-1970
console.log(hashT.retrieve('Ricky Ticky Tavi')); //987-589-1970
console.log(hashT.retrieve('Alex Hawkins')); //510-599-1930
console.log(hashT.retrieve('Lebron James')); //null

3
看起来不错。现在,请解释为什么这很有用,并且可能比这里的所有其他答案更适合。
Thomas Tempelmann '16

1

您可以使用以下方法创建一个:

var dictionary = { Name:"Some Programmer", Age:24, Job:"Writing Programs"  };

//Iterate Over using keys
for (var key in dictionary) {
  console.log("Key: " + key + " , " + "Value: "+ dictionary[key]);
}

//access a key using object notation:
console.log("Her Name is: " + dictionary.Name)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.