概述
如果你的 JavaScript 经验丰富的话,应该会了解对象是创建无序键/值对数据结构 [也称为 映射(map)] 的主要机制。但是,对象作为映射的主要缺点是不能使用非字符串值作为键。
举例来说,考虑:
1 | var m = {}; |
这里发生了什么? x 和 y 两个对象字符串化都是 "[object Object]",所以 m 中只设置了一个键。
Map
在ES6中引入了 Map(..):
1 | var m = new Map(): |
这里唯一的缺点就是不能使用方括号 [ ] 语法设置和获取值,但完全可以使用 get(..) 和 set(..) 方法完美代替。
要从 map 中删除一个元素,不要使用 delete 运算符,而是要使用 delete() 方法:
1 | m.set( x, "foo" ); |
你可以通过 clear() 清除整个 map 的内容。要得到 map 的长度(也就是键的个数),可以 使用 size 属性(而不是 length):
1 | m.set( x, "foo" ); |
Map(..) 构造器也可以接受一个 iterable,这个迭代器必须产生一列数组,每个数组的第一个元素是键,第二个元素是值。这种迭代的形式和 entries() 方法产生的形式是完全一样的,这使得创建一个 map 的副本很容易:
1 | var m2 = new Map( m.entries() ); |
因为 map 的实例是一个 iterable,它的默认迭代器与 entries() 相同,所以我们更推荐使用 后面这个简短的形式。
当然,也可以在 Map(..) 构造器中手动指定一个项目(entry)列表(键 / 值数组的数组):
1 | var x = { id: 1 }, |
Map取值
要从map 中得到一列值,可以使用 values(..),它会返回一个迭代器。比如spread 运算符 … 和 for..of 循环。另外, Array.from(..) 方法。考虑:
1 | var m = new Map(); |
可以在一个 map 的项目上使用 entries() 迭代(或者默认 map 迭代器):
1 | var m = new Map(); |
Map键值
要得到一列键,可以使用 keys(),它会返回 map 中键上的迭代器:
1 | var m = new Map(); |
要确定一个 map 中是否有给定的键,可以使用 has(..)方法:
1 | var m = new Map(); |
map 的本质是允许你把某些额外的信息(值)关联到一个对象(键)上,而无需把这个信 息放入对象本身。
对于 map 来说,尽管可以使用任意类型的值作为键,但通常我们会使用对象,因为字符串 或者其他基本类型已经可以作为普通对象的键使用。换句话说,除非某些或者全部键需要是对象,否则可以继续使用普通对象作为影射,这种情况下 map 才更加合适。
- Tips:如果使用对象作为映射的键,这个对象后来被丢弃(所有的引用解除),试图让垃圾回收(
GC)回收其内存,那么map本身仍会保持其项目。你需要 从map中移除这个项目来支持GC,这时候就需要WeakMap。
WeakMap
WeakMap 是 map 的变体,二者的多数外部行为特性都是一样的,区别在于内部内存分配 (特别是其 GC)的工作方式。WeakMap(只)接受对象作为键。这些对象是被弱持有的,也就是说如果对象本身被垃圾回收的话,在 WeakMap 中的这个项目也会被移除。然而我们无法观测到这一点,因为对象被垃圾回收的唯一方式是没有对它的引用了。但是一旦不再有引用,你也就没有对象引 用来查看它是否还存在于这个 WeakMap 中了。
除此之外,WeakMap 的 API 是类似的,尽管要更少一些:
1 | var m = new WeakMap(); |
WeakMap 没有 size 属性或 clear() 方法,也不会暴露任何键、值或项目上的迭代器。所以即使你解除了对 x 的引用,它将会因 GC 时这个条目被从 m 中移除,也没有办法确定这一事实。所以你就相信 JavaScript 所声明的吧
和 Map 一样,通过 WeakMap 可以把信息与一个对象软关联起来。而在对这个对象没有完 全控制权的时候,这个功能特别有用,比如 DOM 元素。如果作为映射键的对象可以被删除,并支持垃圾回收,那么 WeakMap 就更是合适的选择了。
需要注意的是,WeakMap 只是弱持有它的键,而不是值。考虑:
1 | var m = new WeakMap(); |