JavaScript哈希表的等价实现

417

正如在这个答案的第3个更新中所明确的那样,这种表示法:

var hash = {};
hash[X]

在 JavaScript 中,X 对象并没有被真正进行哈希,实际上它只是将 X 转换为字符串(如果它是一个对象,则通过 .toString() 进行转换,或者对于各种原始类型使用其他内置转换),然后在 "hash" 中查找该字符串,而不是对其进行哈希。也不会检查对象的相等性 - 如果两个不同的对象具有相同的字符串转换,则它们将互相覆盖。

鉴于此,是否存在有效的 JavaScript 哈希映射实现?

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


2
@Claudiu:抱歉修改了标题,但是“Map”确实很误导人。如果您不同意,请回滚,我并不想表现得高人一等。 :) - Tomalak
6
@Claudiu:你对JavaScript问了很多问题。好问题。我喜欢这样。 - some
2
@Claudiu:另外,你能给我提供你所参考的谷歌搜索结果链接吗?不同地区的谷歌会返回不同的结果,你所提到的实现甚至在我的搜索结果中都没有出现。 - Tomalak
@Tomalak:我正想写完全相同的东西! - some
谢谢大家。我已经加入了Google搜索。@某些人:是的,我刚开始学习这门语言。它很有趣! - Claudiu
4
不,不要链接到谷歌。链接到你所说的页面(你正好通过谷歌找到了它)。链接到谷歌会遇到与解释搜索关键词相同的问题:谷歌根据地理位置或搜索历史自定义结果,谷歌的结果会随时间改变(目前,这是该搜索的顶级结果),以及其他可能导致它显示不同结果的因素。 - Jasper
17个回答

429
手动为对象进行哈希处理,将结果字符串用作普通JavaScript字典的键。毕竟,您最了解使对象独特的内容。这就是我所做的。
示例:
var key = function(obj){
  // Some unique object-dependent key
  return obj.totallyUniqueEmployeeIdKey; // Just an example
};

var dict = {};

dict[key(obj1)] = obj1;
dict[key(obj2)] = obj2;

这样,您就可以控制由JavaScript完成的索引,而无需进行繁重的内存分配和溢出处理。
当然,如果您真正想要“工业级解决方案”,您可以构建一个由关键函数参数化的类,并具有容器的所有必要API,但是……我们使用JavaScript,并试图保持简单和轻量级,因此这个功能性解决方案简单且快速。
关键函数可以像选择对象的正确属性一样简单,例如,一个键或一组已经唯一的键,一组在一起唯一的键的组合,或者像DojoX编码DojoX UUID中使用一些加密哈希函数那样复杂。虽然后者的解决方案可能会产生唯一的键,但我个人尽量避免使用它们,特别是如果我知道什么使我的对象唯一。 2014年更新:在2008年回答过这个简单的解决方案仍需要更多解释。让我用问答形式澄清这个想法。 你的解决方案没有真正的哈希。它在哪里? JavaScript是一种高级语言。其基本原语(Object)包括一个哈希表来保存属性。这个哈希表通常是用低级语言编写的,以提高效率。使用一个带有字符串键的简单对象,我们使用了一个高效实现的哈希表,而无需进行任何努力。

你怎么知道他们用哈希表?

有三种主要方法可以通过键来定位对象的集合:

  • 无序。在这种情况下,要通过其键检索对象,我们必须遍历所有键,找到它时停止。平均需要n/2次比较。
  • 有序。
    • 示例1:排序数组 - 进行二分搜索后,平均会进行~log2(n)次比较。好得多。
    • 示例2:树。再次尝试将是~log(n)次。
  • 哈希表。平均需要常数时间。比较:O(n) vs. O(log n) vs. O(1)。太棒了。

显然,JavaScript对象以某种形式使用哈希表处理一般情况。

浏览器供应商真的使用哈希表吗?

真的。

他们处理冲突吗?

是的。请参见上面的内容。如果您发现不相等字符串上有冲突,请毫不犹豫地向供应商报告错误。

那么你的想法是什么?

如果要对对象进行哈希,请找出使其唯一的内容并将其用作键。不要尝试计算实际哈希或模拟哈希表——它已经由底层JavaScript对象高效处理。

使用JavaScript的Object与此键配合使用,以利用其内置的哈希表,同时避免与默认属性可能发生冲突。

以下是一些示例:

  • 如果您的对象包括唯一的用户名,请将其用作键。
  • 如果它包括唯一的客户编号,请将其用作键。
    • 如果它包括像美国SSNs或护照号码这样的唯一政府颁发的数字,并且您的系统不允许重复,请将其用作键。
  • 如果一组字段的组合是唯一的,请将其用作键。
    • 美国州缩写+驾驶执照号码是一个很好的键。
    • 国家缩写+护照号码也是一个很好的键。
  • 一些字段或整个对象上的函数可以返回唯一值,请将其用作键。

我使用了您的建议并缓存了所有对象,使用用户名。但是有个聪明人名字叫"toString",这是一个内置属性!现在我该怎么办?

显然,如果生成的键值只包含拉丁字符,则应该采取一些措施。例如,在开头或结尾添加任何您喜欢的非拉丁Unicode字符以避免与默认属性冲突:"#toString","#MarySmith"。如果使用复合键,请使用某种非拉丁分隔符分隔键组件:"name,city,state"。
通常,这是我们必须创造性地选择具有给定限制(唯一性,潜在与默认属性冲突)的最简单的键的地方。
注意:唯一键根据定义不会发生冲突,而潜在的哈希冲突将由基础Object处理。
“为什么你不喜欢工业解决方案?”
在我看来,最好的代码根本没有代码:它没有错误,不需要维护,易于理解,并且可以立即执行。我看到的所有“JavaScript中的哈希表”都超过100行代码,并涉及多个对象。将其与dict[key] = value进行比较。
另一点:使用JavaScript和完全相同的原始对象来实现已经实现的内容,能否打败用低级语言编写的原始对象的性能?
“我仍然想要哈希我的对象而没有任何键!”
我们很幸运:ECMAScript 6(于2015年6月发布)定义了mapset
从定义上看,它们可以使用对象的地址作为键,这使得对象在没有人工键的情况下立即变得不同。然而,两个不同但完全相同的对象将被映射为不同的对象。
来自MDN的比较分解:
对象和 Map 类似,两者都允许你将键与值设置为一一对应的关系,获取这些值,删除键,并检测某个键是否有值。因此(并且因为没有内置的替代方案),历史上一直将对象用作 Map;然而,有一些重要的区别使得在某些情况下使用 Map 更可取:
- 对象的键是字符串和符号,而 Map 的键可以是任何值,包括函数、对象和任何基本类型。 - Map 中的键是有序的,而添加到对象中的键则没有顺序。因此,在迭代时,Map 对象按插入顺序返回键。 - 您可以使用 size 属性轻松获取 Map 的大小,而必须手动确定对象中属性的数量。 - Map 是可迭代的,因此可以直接进行迭代,而迭代对象则需要以某种方式获取其键并对其进行迭代。 - 对象具有原型,因此映射中可能存在默认键,如果不小心,则可能与您的键发生冲突。从 ES5 开始,可以通过使用 map = Object.create(null) 来绕过此问题,但这很少被使用。 - 在涉及频繁添加和删除键值对的情况下,Map 可能表现更好。

13
这张地图似乎不太正常,因为它没有处理碰撞。如果发生这种情况:hash(obj1) == hash(obj2),你会丢失数据。请注意处理。 - beefeather
36
当"PAUL AINLEY"和"PAULA INLEY"同时在你的系统注册时,愿天堂帮助你... - Matt R
37
实际上,即使使用模拟哈希函数,@MattR的示例也能正常工作,读者应该意识到一个过度简化和非现实的哈希函数仅仅是用作占位符来演示一种不同的技巧。代码注释和答案本身都强调了这一点并且最后一段回答讨论了适当键的选择。 - Eugene Lazutkin
6
@EugeneLazutkin -- 很抱歉,您仍然是错的。您的示例仍然容易发生哈希碰撞。不要认为仅仅将姓放在名字前面就能解决问题! - Matt R
4
@EugeneLazutkin,大多数人并没有注意到你之前已经回答过这个问题,甚至在ES6出现之前就回答了它。让我祝贺你对JavaScript知识的深刻理解。 - Gabriel Andrés Brancolini
显示剩余35条评论

176

问题描述

JavaScript没有内置的通用map类型(有时称为关联数组字典),该类型允许通过任意键访问任意值。 JavaScript的基本数据结构是对象,一种特殊类型的映射,它仅接受字符串作为键,并具有特殊的语义,如原型继承、getter和setter以及其他一些魔法。

当使用对象作为映射时,您必须记住,键将通过toString()转换为字符串值,这导致将5'5'映射到相同的值,所有未覆盖toString()方法的对象都会被索引为'[object Object]'。如果您不检查hasOwnProperty(),还可能无意中访问其继承属性。

JavaScript的内置array类型没有帮助:JavaScript数组不是关联数组,而只是带有一些更多特殊属性的对象。如果您想知道为什么它们不能用作映射,请看看这里

Eugene的解决方案

Eugene Lazutkin已经描述了使用自定义哈希函数生成唯一字符串的基本思路,这些字符串可以用作字典对象的属性查找相关值。这很可能是最快的解决方案,因为对象在内部实现为哈希表

  • 注意:哈希表(有时称为哈希映射)是使用支持数组和通过数字哈希值进行查找的特定映射概念的实现。运行时环境可能使用其他结构(如搜索树跳表)来实现JavaScript对象,但由于对象是基本数据结构,因此应该进行足够的优化。

为了为任意对象获取唯一的哈希值,一种可能性是使用全局计数器,并将哈希值缓存在对象本身中(例如,在名为__hash的属性中)。

一个同时适用于原始值和对象的哈希函数是:

function hash(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
}

hash.current = 0;

这个函数可以像Eugene描述的那样使用。为了方便起见,我们将进一步将其包装在一个Map类中。

我的Map实现

以下实现还将键值对存储在双向链表中,以便快速迭代键和值。要提供自己的哈希函数,可以在创建后覆盖实例的hash()方法。

// Linking the key-value-pairs is optional.
// If no argument is provided, linkItems === undefined, i.e. !== false
// --> linking will be enabled
function Map(linkItems) {
    this.current = undefined;
    this.size = 0;

    if(linkItems === false)
        this.disableLinking();
}

Map.noop = function() {
    return this;
};

Map.illegal = function() {
    throw new Error("illegal operation for maps without linking");
};

// Map initialisation from an existing object
// doesn't add inherited properties if not explicitly instructed to:
// omitting foreignKeys means foreignKeys === undefined, i.e. == false
// --> inherited properties won't be added
Map.from = function(obj, foreignKeys) {
    var map = new Map;

    for(var prop in obj) {
        if(foreignKeys || obj.hasOwnProperty(prop))
            map.put(prop, obj[prop]);
    }

    return map;
};

Map.prototype.disableLinking = function() {
    this.link = Map.noop;
    this.unlink = Map.noop;
    this.disableLinking = Map.noop;
    this.next = Map.illegal;
    this.key = Map.illegal;
    this.value = Map.illegal;
    this.removeAll = Map.illegal;

    return this;
};

// Overwrite in Map instance if necessary
Map.prototype.hash = function(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
};

Map.prototype.hash.current = 0;

// --- Mapping functions

Map.prototype.get = function(key) {
    var item = this[this.hash(key)];
    return item === undefined ? undefined : item.value;
};

Map.prototype.put = function(key, value) {
    var hash = this.hash(key);

    if(this[hash] === undefined) {
        var item = { key : key, value : value };
        this[hash] = item;

        this.link(item);
        ++this.size;
    }
    else this[hash].value = value;

    return this;
};

Map.prototype.remove = function(key) {
    var hash = this.hash(key);
    var item = this[hash];

    if(item !== undefined) {
        --this.size;
        this.unlink(item);

        delete this[hash];
    }

    return this;
};

// Only works if linked
Map.prototype.removeAll = function() {
    while(this.size)
        this.remove(this.key());

    return this;
};

// --- Linked list helper functions

Map.prototype.link = function(item) {
    if(this.size == 0) {
        item.prev = item;
        item.next = item;
        this.current = item;
    }
    else {
        item.prev = this.current.prev;
        item.prev.next = item;
        item.next = this.current;
        this.current.prev = item;
    }
};

Map.prototype.unlink = function(item) {
    if(this.size == 0)
        this.current = undefined;
    else {
        item.prev.next = item.next;
        item.next.prev = item.prev;
        if(item === this.current)
            this.current = item.next;
    }
};

// --- Iterator functions - only work if map is linked

Map.prototype.next = function() {
    this.current = this.current.next;
};

Map.prototype.key = function() {
    return this.current.key;
};

Map.prototype.value = function() {
    return this.current.value;
};

示例

以下脚本,

var map = new Map;

map.put('spam', 'eggs').
    put('foo', 'bar').
    put('foo', 'baz').
    put({}, 'an object').
    put({}, 'another object').
    put(5, 'five').
    put(5, 'five again').
    put('5', 'another five');

for(var i = 0; i++ < map.size; map.next())
    document.writeln(map.hash(map.key()) + ' : ' + map.value());

生成以下输出:
string spam : eggs
string foo : baz
object 1 : an object
object 2 : another object
number 5 : five again
string 5 : another five

进一步的考虑

PEZ建议重写toString()方法,预计用我们的哈希函数。这是不可行的,因为它对基本值不起作用(改变基本值的toString()是一个非常糟糕的想法)。如果我们想让toString()返回任意对象的有意义的值,我们将不得不修改Object.prototype,有些人(包括我自己)认为这是禁止的。


我的Map实现的当前版本以及其他JavaScript好玩的东西可以从这里获得


ES5已经废弃了callee的使用(http://goo.gl/EeStE)。相反,我建议使用`Map._counter = 0,并在Map构造函数中执行this._hash = 'object ' + Map._counter++。然后,hash()变成return (value && value._hash) || (typeof(value) + ' ' + String(value));`。 - broofa
代码链接已经失效:http://mercurial.intuxication.org/hg/js-hacks/raw-file/tip/map.js - ahcox
嗨,@Christoph,你能更新一下我可以找到你的地图实现的链接吗? - NumenorForLife
2
@jsc123:我会调查一下 - 现在你可以在https://pikacode.com/mercurial.intuxication.org/js-hacks.tar.gz获取存储库的转储。 - Christoph

84

如今有许多外部库可以提供非常好的解决方案:

JavaScript 也有其提供的语言内置的 Map


6
这是前进迈向21世纪的方式。很遗憾,在用一些难看的自制地图写完代码后才发现了你的帖子。我们需要更多的投票支持你的回答。 - Phung D. An
1
Collections.js 有一些实现,但我在 underscore.js 或 lodash 中找不到任何实现...你在 underscore 中提到的会有用吗? - Codebling
@CodeBling 没有头绪。我觉得我把它和 map 函数搞混了。我会从答案中删除它。 - Jamel Toms
3
没问题。任何考虑使用 Collections.js 的人都应该知道它以一种有问题的方式修改了全局 Array、Function、Object 和 Regexp 原型(可以在这里看到我遇到的问题)。虽然一开始我非常喜欢 collections.js(也就是这个答案),但使用它存在的风险太高了,所以我放弃了它。只有 kriskowal 的 collections.js v2 分支(特别是 v2.0.2+)消除了全局原型修改的问题,是安全的可用的。 - Codebling

46
根据ECMAScript 2015(ES6),标准JavaScript具有Map实现。更多信息可在此处找到:here。 基本用法:
var myMap = new Map();
var keyString = "a string",
    keyObj = {},
    keyFunc = function () {};

// Setting the values
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

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

1
但它是否使用基于哈希的实现?显然,这对性能至关重要,在其他语言中使用哈希映射的情况下。 - Adam Burley
它使用底层对象ID。因此,如果您说o = {}map.set(o, 42)并改变o,那么map.get(o)仍将起作用。 - Luke Miles

31

这里有一种简单方便的方法,可以使用类似于Java Map的东西:

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. ...

3
这仅适用于字符串键。我相信OP想要使用任何类型的键。 - fractor

25

您可以使用 ECMAScript 6 的 WeakMapMap

  • WeakMap 是一种键/值映射,其中键是对象。
  • Map 对象是简单的键/值映射。任何值(包括对象和原始值)都可以用作键或值。

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


在2019年,它们得到了很好的支持,并且拥有令人惊叹的方法!https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map#Methods - Juanma Menendez

13

您需要在一些内部状态中存储对象/值对的联排:

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)级别的。尤金的例子是获得哈希速度符合实际预期的唯一方法。

另一种方法,沿用尤金的答案,是以某种方式为所有对象附加唯一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];
}

这个版本似乎只是稍微快一点,但理论上对于大数据集来说它将会显著提高速度。


一个关联数组,即一个由二元组组成的数组,是一个 Map,而不是 HashMap;HashMap 是使用哈希提升性能的 Map。 - Erik Kaplun
1
真的,但为什么要在这个问题上纠结呢?在JavaScript中无法创建真正的哈希映射表,因为你无法获取对象的内存地址。JavaScript内置的对象键/值对(在我的第二个例子中使用)可能充当HashMaps,但不一定如此,因为查找实现取决于浏览器中使用的运行时。 - pottedmeat

11

很不幸,之前的回答都无法满足我的情况:不同的键对象可能具有相同的哈希码。因此,我写了一个简单的类似Java的HashMap版本:

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

HashMap.prototype.put = function(key, value) {
    var hashCode = key.hashCode();
    var bucket = this.buckets[hashCode];
    if (!bucket) {
        bucket = new Array();
        this.buckets[hashCode] = bucket;
    }
    for (var i = 0; i < bucket.length; ++i) {
        if (bucket[i].key.equals(key)) {
            bucket[i].value = value;
            return;
        }
    }
    bucket.push({ key: key, value: value });
}

HashMap.prototype.get = function(key) {
    var hashCode = key.hashCode();
    var bucket = this.buckets[hashCode];
    if (!bucket) {
        return null;
    }
    for (var i = 0; i < bucket.length; ++i) {
        if (bucket[i].key.equals(key)) {
            return bucket[i].value;
        }
    }
}

HashMap.prototype.keys = function() {
    var keys = new Array();
    for (var hashKey in this.buckets) {
        var bucket = this.buckets[hashKey];
        for (var i = 0; i < bucket.length; ++i) {
            keys.push(bucket[i].key);
        }
    }
    return keys;
}

HashMap.prototype.values = function() {
    var values = new Array();
    for (var hashKey in this.buckets) {
        var bucket = this.buckets[hashKey];
        for (var i = 0; i < bucket.length; ++i) {
            values.push(bucket[i].value);
        }
    }
    return values;
}

注意:关键对象必须“实现”hashCode()equals()方法。


10
使用 new Array() 而不是 [] 的偏好是为了确保你的代码具有绝对的 Java 风格吗? :) - Erik Kaplun

6

我已经实现了一个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")
}

3
您的代码似乎不能将同一个对象放入多个HashMap中。 - Erik Kaplun

5
在 ECMAScript 6 中,您可以使用 WeakMap
示例:
var wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // A value can be anything, including an object or a function
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // Keys and values can be any objects. Even WeakMaps!

wm1.get(o2); // "azerty"
wm2.get(o2); // Undefined, because there is no value for o2 on wm2
wm2.get(o3); // Undefined, because that is the set value

wm1.has(o2); // True
wm2.has(o2); // False
wm2.has(o3); // True (even if the value itself is 'undefined')

wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // Undefined, because wm3 was cleared and there is no value for o1 anymore

wm1.has(o1);   // True
wm1.delete(o1);
wm1.has(o1);   // False

但是:

由于引用较弱,WeakMap的键不可枚举(即没有方法可以给您列出键的列表)。


哦,赞美耶稣,他们终于在JavaScript中添加了弱引用。是时候了...为此加1分,但实际使用起来会很糟糕,因为这些引用是弱引用。 - Claudiu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接