可达性
通常,当对象、数组之类的数据结构在内存中时,它们的子元素,如对象的属性、数组的元素都被认为是可达的。
如果使用对象作为常规 Map
的键,那么当 Map
存在时,该对象也将存在。它会占用内存,并且不会被(垃圾回收机制)回收。
例如:
let john = {name: "John”};
let map = new Map();
map.set(john, '...');
//john 被存储在了 map 中,
//我们可以使用 map.keys() 来获取它
WeakMap
在这方面有着根本的不同。它不会阻止垃圾回收机制作为对键的对象(key object)的回收。
WeakMap
WeakMap
和 Map
的第一个不同点就是,WeakMap
的键必须是对象,不能是原始值:
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "ok"); //正常工作(以对象为键)
//不能使用字符串作为键
weakMap.set("test", "Whoops"); //Error,因为 “test” 不是一个对象
现在,如果我们在 weakMap 中使用一个对象作为键,并且没有其他对这个对象的引用 —— 该对象将会被从内存(和map)中自动清除。
let john = {name: "John"};
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; //覆盖引用
//john 被从内存中删除了!
如果 john
仅仅是作为 WeakMap
的键而存在 —— 它将会从 map (和内存中)自动删除。
WeakMap
不支持迭代以及 keys()
,values()
和 entries()
方法。所以没有办法获取 WeakMap
的所有键或值。
WeakMap
只有以下的方法:
- `weakMap.get(key)
- `weakMap.set(key, value)
- `weakMap.delete(key)
- `weakMap.has(key)
为什么会有这种限制呢?这是技术的原因。如果一个对象丢失了其它所有引用(就像上面示例中的 john),那么它就会被垃圾回收机制自动回收。但是在从技术的角度并不能准确知道 何时会被回收。
这些都是由 JavaScript 引擎决定的。JavaScript 引擎可能会选择立即执行内存清理,如果现在正在发生很多删除操作,那么 JavaScript 引擎可能就会选择等一等,稍后再进行内存清理。因此,从技术上讲,WeakMap
的当前元素的数量是未知的。JavaScript 引擎可能清理了其中的垃圾,可能没清理,也可能清理了一部分。因此,暂不支持访问 WeakMap
的所有键/值的方法。
那么,在哪里我们会需要这样的数据结构呢?
WeakSet
WeakSet
的表现类似:
- 与
Set
类似,但是我们只能向WeakSet
添加对象(而不是原始值)。 - 对象只有在其它某个(些)地方被访问的时候,才能留在 set 中。
- 跟
Set
一样,WeakSet
支持add
,has
和delete
方法,但不支持size
和keys
()`,并且不可迭代。
变“弱(weak)”的同时,它也可以作为额外的存储空间。但并非针对任意数据,而是针对“是/否”的事实。WeakSet
的元素可能代表着有关该对象的某些信息。
例如,我们可以将用户添加到 WeakMap
中,以追踪访问过我们网站的用户:
let visitedSet = new WeakSet();
let john = {name: "John"};
let pete = {name: "Pete"};
let mary = {name: "Mary"};
visitedSet.add(john); //John 访问了我们
visitedSet.add(pete); //然后是 Pete
visitedSet.add(john); //John 再次访问
//visitedSet 现在有两个用户了
//检查 John 是否来访过?
alert(visitedSet.has(john)); //true
//检查 Mary 是否来访过?
alert(visitedSet.has(mary)); //false
john = null;
//visitedSet 将被自动清理(即自动清理其中已失效的值 john)
WeakMap
和 WeakSet
最明显的局限性就是不能迭代,并且无法获取所有当前内容。那样可能会造成不便,但是并不会阻止 WeakMap/WeakSet
完成其主要工作 —— 成为在其它地方管理/存储“额外”的对象数据。