【问题标题】:How to do associative array/hashing in JavaScript如何在 JavaScript 中进行关联数组/散列
【发布时间】:2010-09-12 22:22:57
【问题描述】:

我需要像在 C# 中那样使用 JavaScript 存储一些统计数据:

Dictionary<string, int> statistics;

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

JavaScript 中有 Hashtable 或类似 Dictionary&lt;TKey, TValue&gt; 的东西吗?
我怎样才能以这种方式存储值?

【问题讨论】:

标签: javascript dictionary hashtable


【解决方案1】:

使用JavaScript objects as associative arrays

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

创建一个对象

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 */
}

【讨论】:

  • 请注意,作者使用new Array() 初始化“关联数组”的方法是不受欢迎的。文章最终提到了它的缺点,并建议将 new Object(){} 作为首选替代方案,但这已接近尾声,我担心大多数读者不会走得那么远。
  • 失败。 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 中很容易。
  • 嗯,在 JS 中我们可以通过检查相等性或定义 equals 方法来验证的唯一方法是:Point.prototype.equals = function(obj) { return (obj instanceof Point) &amp;&amp; (obj.x === this.x) &amp;&amp; (obj.y === this.y); };
  • @Leo console.log({A:'B',C:'D'}[foo]) 应该给你 A B。
  • @Leo 这个例子似乎是错误的。字典的for... in 将遍历其键,因此Object.keys 似乎放在那里。 Object.keys 返回字典键的数组,for... in 用于数组循环 “键”,对于数组而言,键是它的索引,而不是它的值。
【解决方案2】:
var associativeArray = {};
associativeArray["one"] = "First";
associativeArray["two"] = "Second";
associativeArray["three"] = "Third";

如果您来自面向对象的语言,您应该查看this article

【讨论】:

  • 你也可以用更少的行来做到这一点: var associativeArray = {"one" : "First", "two" : "second", "three" : "Third"};然后 associativeArray["one"] 返回 "First", assocativeArray["four"] 返回 null。
【解决方案3】:

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

  • 一个对象有一个原型,所以映射中有默认键。
  • 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。

【讨论】:

  • 希望几年后这将是投票最多的答案。
  • @CameronLee 肯定会的
  • 这个Map 在你的键是一个对象但应该按值而不是引用比较时几乎没有用。
  • 在写完这个答案一年多之后,“所有现代浏览器都支持 Map”仍然不是真的。只有在桌面上,您才能指望至少基本的地图支持。不在移动设备上。例如,Android 浏览器根本不支持地图。即使在桌面上,一些实现也不完整。例如,IE11 仍然不支持通过“for...of...”进行枚举,所以如果你想要与 IE 兼容,就必须使用令人作呕的 .forEach 组件。此外,JSON.stringify() 在我尝试过的任何浏览器中都不适用于 Map。初始化器在 IE 或 Safari 中也不起作用。
  • 有优秀的浏览器支持。再检查一遍。无论如何,这很容易填充,因此本机浏览器支持不是问题。
【解决方案4】:

除非您有特定的理由不这样做,否则请使用普通对象。 JavaScript 中的对象属性可以使用 hashtable 样式的语法来引用:

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

foobar 元素现在都可以引用为:

hashtable['foo'];
hashtable['bar'];

// Or
hashtable.foo;
hashtable.bar;

当然,这确实意味着您的键必须是字符串。如果它们不是字符串,它们会在内部转换为字符串,所以它可能仍然有效。您的里程可能会有所不同。

【讨论】:

  • 作为整数的键对我没有任何问题。 stackoverflow.com/questions/2380019/…
  • Jonas:请记住,在设置属性时,您的整数会转换为字符串:var hash = {}; hash[1] = "foo"; alert(hash["1"]); alerts "foo"。
  • 如果您的键之一是“proto”或“parent”怎么办?
  • 请注意,对象不能用作 JavaScript 中的键。好吧,它们可以,但是它们被转换为它们的字符串表示形式,因此任何对象最终都会作为完全相同的键。请参阅下面的@TimDown 的 jshashtable 建议。
  • 此示例令人困惑,因为您在两个实例中同时使用 foo 和 bar 作为键和值。更清楚地表明var dict = {}; dict.key1 = "val1"; dict["key2"] = "val2"; dict 的key1 元素可以被dict["key1"]dict.key1 等效引用。
【解决方案5】:

由于 JavaScript 中的每个对象都表现得像 - 并且通常被实现为 - 一个哈希表,所以我只是使用它......

var hashSweetHashTable = {};

【讨论】:

  • 投反对票,因为它没有显示如何实际访问“哈希表”中的值。
  • 我迟到了 9 年(我当时对编程一无所知,更不用说这个网站了),但是......如果你想在地图上存储点怎么办,并且需要查看地图上的某个点是否已经存在某些东西?在这种情况下,您最好使用 HashTable,通过坐标查找(object,而不是 string)。
  • 如果设置了foo@MikeWarren if (hashSweetHashTable.foo) 应该进入 if 块。
【解决方案6】:

在 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 中,我们可以使用 hasOwnProperty 之类的:

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

【讨论】:

  • 为我投票,我不必写关于hasOwnProperty的答案
【解决方案7】:

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

【讨论】:

  • 在我发现这个之前,我花了多少个小时来纠结对象不能真正用作 JS 样式对象作为关联数组的键这一事实?谢谢你,蒂姆。
  • Flash/AS3 字典以及大多数其他语言都支持将对象引用作为键。 JavaScript 还没有实现它,但我认为它在未来的规范中作为某种 Map 类。同时再次使用 polyfill;标准如此之多。哦,等等……终于在 2015 年,Map 似乎已经到来:stackoverflow.com/a/30088129/88409,并且受到“现代”浏览器的支持,哈哈:kangax.github.io/compat-table/es6/#Map(并没有得到广泛的支持)。仅落后 AS3 十年。
  • Tim,也许您应该更新 jshashtable 以在可用的情况下使用 Map()。
  • @DaveBurton:好计划。我一有时间就会这样做。
【解决方案8】:

注意:

几年前,我实现了以下哈希表,它具有Map 类所缺少的一些功能。然而,情况不再如此——现在,可以遍历 Map 的条目,获取其键或值或两者的数组(不过,这些操作是通过复制到新分配的数组来实现的——这是一种浪费内存和它的时间复杂度总是和O(n)一样慢),删除给定键的特定项目,并清除整个地图。
因此,我的 hashtable 实现仅用于兼容性目的,在这种情况下,基于此编写适当的 polyfill 是一种更明智的方法。


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的索引,根据已经添加的条目的顺序。

    参数:
    key:获取索引的key。


  • indexOfValue(value)

    返回指定值的索引,根据已添加条目的顺序。

    参数:
    value:获取索引的值。

    备注:
    值按身份进行比较。


  • entryAt(index)

    返回一个具有keyvalue 属性的对象,表示指定索引处的条目。

    参数:
    index:要获取的条目的索引。


  • containsKey(key)

    返回哈希表是否包含指定的键。

    参数: key:要找的钥匙。


  • containsValue(value)

    返回哈希表是否包含指定的值。

    参数:
    value:要查找的值。


  • forEach(iterator)

    遍历哈希表中的所有条目,调用指定的iterator

    参数:
    iterator:具有keyvalueindex三个参数的方法,其中index表示根据订单已添加。

属性:

  • length只读

    获取哈希表中条目的计数。

  • keys只读

    获取哈希表中所有键的数组。

  • values只读

    获取哈希表中所有值的数组。

  • entries只读

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

【讨论】:

    【解决方案9】:
    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;
        }
    }
    

    【讨论】:

    • 对于反对此投票的人,您能评论一下原因吗?此答案发布于 2011 年,而不是当前日期。
    • 我没有投反对票,但是……您不应该将数组用作对象。不是 100% 确定这是否是您的意图。在未删除的数组上使用切片重新索引;删除是可以的,但会设置为未定义——最好是明确的;在对象上也使用 = undefined b/c 它更快(但内存更多)。简而言之:始终使用对象:{} 而不是数组:[]new Array() 如果您打算使用字符串键,否则 js 引擎会出现问题——它会看到 1 个变量的 2 种类型,这意味着没有优化,否则它将与数组一起运行并意识到它必须更改为对象(可能重新分配)。
    • 就像 Alex Hawkins 的回答一样,请提供一些解释,为什么这个看起来相当复杂的代码实际上是有用的,并且比这里给出的其他较短的答案更好。
    【解决方案10】:

    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) {
    
          // Override 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' ] ] ]
    */
    
    // Override 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 THE HASH TABLE HAS NOW DOUBLED IN SIZE UPON REACHING 75% CAPACITY, i.e. 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
    

    【讨论】:

    • 看起来不错。现在,还请解释为什么这很有用,并且可能比这里的所有其他答案更适合。
    • 不是针对哈希表的整个点将数据存储在数组中吗?
    【解决方案11】:

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

    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)

    【讨论】:

      猜你喜欢
      • 2011-07-09
      • 2011-05-01
      • 2011-10-18
      • 1970-01-01
      • 2011-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多