Map
对象用来保存键值对,并且能够记住键的原始插入顺序,任何对象或者原始值都可以作为键或者是值。WeakMap
对象同样用来保存键值对,对于键是弱引用的而且必须为一个对象,而值可以是任意的对象或者原始值。
Map
描述
Map
对象类似于一个普通的键值对的 Object
对象,也是键值对的集合,但是他们之间有一些重要的区别:
描述 | Map | Object |
---|---|---|
意外的键 | Map 默认情况不包含任何键,只包含显式插入的键。 |
一个 Object 有一个原型,原型链上的键名有可能和在对象上的设置的键名产生冲突。 |
键的类型 | 一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。 |
一个 Object 的键必须是一个 String 或是 Symbol。 |
键的顺序 | Map 中的 key 是有序的,当迭代的时候,一个 Map 对象以插入的顺序返回键值。 |
一个 Object 的键的迭代顺序需要通过键的类型与创建的顺序来确定。 |
键值数量 | Map 的键值对个数可以轻易地通过 size 属性获取。 |
Object 的键值对个数只能手动计算。 |
迭代 | Map 是 iterable 的,所以可以直接被迭代。 |
迭代一个 Object 需要以某种方式获取它的键然后才能迭代。 |
性能 | Map 在频繁增删键值对的场景下表现更好。 |
Object 在频繁添加和删除键值对的场景下未作出优化。 |
注:关于一个 Object
的键的迭代顺序问题,在 ES6 以后,对象保留了 String
与 Symbol
的创建顺序,当创建的对象仅有 String
或者 Symbol
时,迭代顺序与创建顺序相同,当对象中两种类型都存在时, String
总是在前,当 String
可以被转换为 Number
时,这些键在迭代时处于最前,且会按照数字的顺序进行迭代。
属性与方法
属性和方法 | 描述 |
---|---|
Map.prototype.constructor |
返回构造函数 |
Map.prototype.size |
返回 Map 对象的键值对的数量。 |
Map.prototype.clear() |
移除 Map 对象的所有键值对 。 |
Map.prototype.delete(key) |
如果 Map 对象中存在该元素,则移除它并返回 true ,否则如果该元素不存在则返回 false 。 |
Map.prototype.entries() |
返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 |
Map.prototype.forEach(callback[, thisArg]) |
按插入顺序,为 Map 对象里的每一键值对调用一次 callback 函数,如果为 forEach 提供了 thisArg ,它将在每次回调中作为 this 值。 |
Map.prototype.get(key) |
返回键对应的值,如果不存在,则返回 undefined 。 |
Map.prototype.has(key) |
返回一个布尔值,表示 Map 实例是否包含键对应的值。 |
Map.prototype.keys() |
返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的键。 |
Map.prototype.set(key, value) |
设置 Map 对象中键的值,返回该 Map 对象。 |
Map.prototype.values() |
返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。 |
Map.prototype[@@iterator]() |
返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 |
示例
const m = new Map();
const stringKey = "s";
const objectKey = {};
m.set(stringKey, "stringValue");
m.set(objectKey, "objectValue");
console.log(m.size); // 2
console.log(m.get(stringKey)); // stringValue
console.log(m.get(objectKey)); // objectValue
for (let [key, value] of m) {
console.log(key, value);
}
/*
s stringValue
{} objectValue
*/
const m2 = new Map([
["stringKey", "stringValue"],
[{}, "objectValue"]
]);
console.log(m2); // Map(2) {"stringKey" => "stringValue", {…} => "objectValue"}
const m3 = new Map([
...m,
...m2,
["stringKey", "coverStringValue"],
[{}, "{} !== {}"],
[NaN, "NaN !== NaN But key(NaN) === key(NaN)"],
]);
console.log(m3); // Map(6) {"s" => "stringValue", {…} => "objectValue", "stringKey" => "coverStringValue", {…} => "objectValue", {…} => "{} !== {}", NaN => "NaN !== NaN But key(NaN) === key(NaN)"}
WeakMap
描述
WeakMap
的 key
只能是 Object
类型,原始数据类型不能作为 key
。 WeakMap
持有的是每个键对象的弱引用,这意味着在没有其他引用存在时垃圾回收能正确进行, WeakMap
用于映射的 key
只有在其没有被回收时才是有效的,正由于弱引用, WeakMap
的 key
是不可枚举的,没有方法能给出所有的 key
。 简单来说,有时需要在某个对象上面存放一些对象,但是这会形成对于这个对象的引用,一旦不再需要这个对象,我们就必须手动删除这个引用,否则垃圾回收机制无法释放对象占用的内存, WeakMap
的设计就是解决这个问题的,它的键所引用的对象都是弱引用,垃圾回收机制不将该引用考虑在内,因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存,此时 WeakMap
里边所对应的键值都会消失,不需要手动删除引用。如果需要在对象上添加对象而又不想干扰垃圾回收机制的话,就可以使用 WeakMap
。
属性与方法
属性和方法 | 描述 |
---|---|
WeakMap.prototype.constructor |
返回构造函数。 |
WeakMap.prototype.delete(key) |
移除 key 的关联对象。 |
WeakMap.prototype.get(key) |
返回 key 关联对象,没有 key 关联对象时返回 undefined 。 |
WeakMap.prototype.has(key) |
根据是否有 key 关联对象返回一个 Boolean 值。 |
WeakMap.prototype.set(key, value) |
在 WeakMap 中设置一组 key 关联对象,返回这个 WeakMap 对象。 |
内存回收实例
WeakMap 示例代码
const wm = new WeakMap();
let key = {};
wm.set(key, new Array(6 * 1024 * 1024)); // 存放一个大数组
console.log(wm.get(key)); // (6291456) [empty × 6291456]
key = null;
console.log(wm.get(key)); // undefined
WeakMap 内存回收实例
/** node --expose-gc **/ // 启动 node 环境 手动调用垃圾回收机制
global.gc(); // 首先调用一次垃圾回收
process.memoryUsage(); // 查看内存占用 heapUsed 约 3M
/*
{
rss: 24076288,
heapTotal: 5222400,
heapUsed: 3027360,
external: 1568683,
arrayBuffers: 99508
}
*/
const wm = new WeakMap();
let key = {};
wm.set(key, new Array(6 * 1024 * 1024)); // 存放一个大数组
console.log(wm.get(key)); // (6291456) [empty × 6291456]
process.memoryUsage(); // heapUsed 约 53M
/*
{
rss: 75124736,
heapTotal: 55558144,
heapUsed: 53937808,
external: 1568930,
arrayBuffers: 140675
}
*/
global.gc(); // 手动执行一次垃圾回收
process.memoryUsage(); // heapUsed 约 53M
/*
{
rss: 74907648,
heapTotal: 55558144,
heapUsed: 53360392,
external: 1568810,
arrayBuffers: 156939
}
*/
key = null; // 解除引用
global.gc(); // 执行垃圾回收
process.memoryUsage(); // heapUsed 约 3M 内存已回收
/*
{
rss: 24391680,
heapTotal: 4960256,
heapUsed: 2960104,
external: 1568814,
arrayBuffers: 181519
}
*/
console.log(wm.get(key)); // undefined
Map 内存回收实例
/** node --expose-gc **/ // 启动 node 环境 手动调用垃圾回收机制
global.gc(); // 首先调用一次垃圾回收
process.memoryUsage(); // 查看内存占用 heapUsed 约 2.6M
/*
{
rss: 23097344,
heapTotal: 8052736,
heapUsed: 2672504,
external: 1531483,
arrayBuffers: 34073
}
*/
const m = new Map();
let key = {};
m.set(key, new Array(6 * 1024 * 1024)); // 存放一个大数组
console.log(m.get(key)); // (6291456) [empty × 6291456]
process.memoryUsage(); // heapUsed 约 53M
/*
{
rss: 73863168,
heapTotal: 55558144,
heapUsed: 53622640,
external: 1531615,
arrayBuffers: 75125
}
*/
global.gc(); // 手动执行一次垃圾回收
process.memoryUsage(); // heapUsed 约 53M
/*
{
rss: 73506816,
heapTotal: 55558144,
heapUsed: 53239920,
external: 1531505,
arrayBuffers: 91399
}
*/
key = null; // 解除引用
global.gc(); // 执行垃圾回收
process.memoryUsage(); // heapUsed 约 53M 内存未回收
/*
{
rss: 73465856,
heapTotal: 55296000,
heapUsed: 53143568,
external: 1531510,
arrayBuffers: 115980
}
*/
console.log(m.get(key)); // undefined // 此处是 undefined,这是因为 key 值的改变,而在这个 Map 实例对象中依然存在 {} => Array 的键值对,且键值对为强引用,内存未回收
console.log(m); // Map(1) {{…} => Array(6291456)}
m.clear(); // 回收内存
global.gc(); // 执行垃圾回收
process.memoryUsage(); // heapUsed 约 2.8M 内存已回收
/*
{
rss: 23887872,
heapTotal: 4960256,
heapUsed: 2883592,
external: 1560363,
arrayBuffers: 156853
}
*/
console.log(m); // Map(0) {}