概述
如果你的 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(); |