原文:http://www.nczonline.net/blog/2012/11/06/ecmascript-6-collections-part-3-weakmaps/
WeakMap
类似于常规的Map
的一点是,它们都是把一个值映射到某个唯一的键上,然后就可以使用这个键获取到与之对应的值.WeakMap
和Map
不同的地方是,它的键只能是对象值而不可以是原始值.虽然这个限制看起来很奇怪,但正是这一点,才让WeakMap
变得很有价值.
一个WeakMap
对象的键只持有其所引用对象的弱引用,弱引用的特点是,它不能阻止垃圾回收器回收其引用的对象.当那个对象被垃圾回收器销毁后,WeakMap
对象中引用它的那个键值对也会被删除.使用WeakMap
最典型的例子就是用在要创建一个与特定的DOM元素相关联的对象的时候.例如,jQuery在程序内部维护着一些对象的缓存,每个缓存引用着一个DOM元素.使用WeakMap
的话,jQuery就可以在某些DOM元素被从文档中删除后就自动释放它们先前占用的内存.
ECMAScript 6中的WeakMap
类型是一个无序的键值对列表,键必须是一个非null的对象(non-null object),值可以是任意类型.
WeakMap
的API很简单,和Map
一样,set()
和get()
分别用来添加数据和获取数据:
var map = new WeakMap(),
element = document.querySelector(".element");
map.set(element, "Original");
// 下面就可以使用了
var value = map.get(element);
console.log(value); // "Original" // 下面删除引用 element.parentNode.removeChild(element);
element = null;
//译者注:下面这句实际上会报错(value is not a non-null object),因为键必须是非null的对象.作者只是为了讲解,看下面.
value = map.get(element);
console.log(value); // undefined
在这个例子中,我们存储了一个键值对.键是一个DOM元素,存储着一个对应的字符串值.随后把那个DOM元素传入get()
方法来获取到存储的字符串值.如果这个DOM元素被从文档中删除了,指向它的变量也被赋值为null
,然后WeakMap
对象中的那个键值对会被自动删除,这时如果再读取那个键值对的话,就会失败.
这个例子有点误导,因为在第二次调用map.get(element)
的时候,实际上是把null
(element
被赋的值)而不是DOM元素的引用传进去了.你不能使用null
来作为WeakMap
对象的键(WeakMap的键只能是对象,null不是对象),这样的代码是会抛出异常的,根本不会执行到console.log语句.上面的代码之所以那么写,只是为了讲解.想要说明那个键值对已经不存在了.不过不幸的是,我们没有任何办法来检测这个键值对是否真的被删除了(因为element
的值已经是null
,我们无法获取到指向那个DOM元素的引用了,假如真能获取到那个DOM对象的引用,传给get方法的话,应该返回undefined).
译者注:由于语文水平实在有限,上面的这段话我无法表达清楚.所以我用我拙劣的水平做了几张图,希望我的理解是对的,也希望你能看懂.
var map = new WeakMap(),变量map和element各自引用了一个对象.
element = document.querySelector(".element");
map.set(element, "Original");map中添加了一个键值对,这个键和变量element同时指向一个DOM对象,只是一个是强引用,一个是弱引用.
element.parentNode.removeChild(element);
element = null;强引用全部主动断掉,最后剩下的弱引用也自动断掉,孤立的DOM元素被回收,WeakMap对象中对应的键值对被自动清除.如果是常规的Map,由于不是弱引用,所以那个DOM对象不会被回收,仍然存在,仍然可以通过get()方法访问到.
WeakMap
对象还有一个has()
方法,可以判断某个对象引用是否作为了自己的键,还有delete()
方法,用来删除一个键值对.
var map = new WeakMap(),
element = document.querySelector(".element");
map.set(element, "Original");
console.log(map.has(element)); // true console.log(map.get(element)); // "Original" map.delete(element);
console.log(map.has(element)); // false console.log(map.get(element)); // undefined
使用delete()
方法删除某个键后,再执行has()
方法的时候会返回false
,执行get()
方法会返回undefined
.
浏览器支持
Firefox和Chrome都已经实现了WeakMap
,可是,在Chrome中,你必须手动开启ECMAScript 6特性:打开chrome://flags
勾选"启用实验性 JavaScript".目前浏览器的实现都完全遵循了当前的strawman[1]规范(而ECMAScript 6的最新草案新添加了一个WeakMap.prototype.clear()
方法,这两个浏览器目前还都没实现).
用途和限制
目前WeakMap
有一个特定的使用情境,那就是在将值映射到一些未来可能被会删除的对象上的时候.WeakMap
的这种"能释放某些无用对象所占用的内存的能力"对于那种将DOM元素包装成为某种自定义对象的JavaScript库来说是很有用的(比如jQuery和YUI).在未来,WeakMap
的标准完全实现且被广泛使用之后,应该会有更多的能用到WeakMap
的地方,所以在短期内,不要因为想不到使用WeakMap
的好点子而苦恼.
在大多数情况下,使用常规的Map
应该是你最合适的选择.WeakMap
有不少限制,比如不能枚举键值对(for of
),也不能得知它们到底包含了多少个键值对(size
).如果你需要这样做,那么就使用常规的Map
.如果你仅仅需要把一些对象作为键,且不需要其他更多的功能,那么WeakMap
是个好的选择.
参考
- WeakMapsStrawman (ECMA)