本文详细对比了JavaScript中Object、Map和WeakMap三种键值存储方式的特性。重点分析了Map在键类型、插入顺序及内存占用上的优势,探讨了其在大量增删操作中的性能表现,并解释了WeakMap通过弱引用实现内存自动回收的机制及其在DOM元数据管理中的应用场景。
前言
在ECMAScript 6标准推出之前,JavaScript开发者通常利用Object对象属性作为键来存储键值对数据,这种方式虽然便捷高效,但存在若干局限性。为此,TC39委员会制定了专门针对键值存储的规范,并在ES6中引入了Map类型,为JavaScript提供了真正的键值存储机制。尽管Map的多数功能可通过Object实现,但两者在细节上存在差异,实际开发中需根据具体需求仔细甄别选用。
一、map 的使用
初始化
Object可通过字面量、构造函数或Object.create()方法创建,而Map仅能通过new关键字配合构造函数实例化。若需在创建Map时初始化数据,可向构造函数传入包含键值对数组的可迭代对象,这些键值对将按迭代顺序插入新实例中:
object 的创建方式
const object = {}
const object1 = new Object()
const object2 = Object.create({})
map 的创建方式
//使用new关键字
const m0 = new Map;
// 使用嵌套数组初始化映射
const m1 = new Map([
["key1", "val1"],
["key2", "val2"],
["key3", "val3"]
]);
alert(m1.size); // 3
// 使用自定义迭代器初始化映射
const m2 = new Map({
[Symbol.iterator]: function*() {
yield ["key1", "val1"];
yield ["key2", "val2"];
yield ["key3", "val3"];
}
});
alert(m2.size); // 3
// 映射期待的键/值对,无论是否提供
const m3 = new Map([[]]);
alert(m3.has(undefined)); // true
alert(m3.get(undefined)); // undefined
map键类型
与Object仅支持数值、字符串或符号作为键不同,Map允许使用任意JavaScript数据类型作为键。Map内部采用SameValueZero比较操作(ECMAScript规范定义,语言层面不可直接调用)来检查键的匹配性,这大致等同于严格对象相等标准。与Object类似,Map中的值类型不受限制。
const m = new Map();
const functionKey = function() {};
const symbolKey = Symbol();
const objectKey = new Object();
m.set(functionKey, "functionValue");
m.set(symbolKey, "symbolValue");
m.set(objectKey, "objectValue");
alert(m.get(functionKey)); // functionValue
alert(m.get(symbolKey)); // symbolValue
alert(m.get(objectKey)); // objectValue
// SameValueZero比较意味着独立实例不冲突
alert(m.get(function() {})); // undefined
顺序与迭代
Map实例与Object的主要区别之一在于其维护键值对的插入顺序,从而支持按插入顺序进行迭代。Map实例提供迭代器(Iterator),可按插入顺序生成[key, value]形式的数组。通过entries()方法(或引用entries()的Symbol.iterator属性)即可获取该迭代器:
const m = new Map([
["key1", "val1"],
["key2", "val2"],
["key3", "val3"]
]);
alert(m.entries === m[Symbol.iterator]); // true
for (let pair of m.entries()) {
alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
for (let pair of m[Symbol.iterator]()) {
alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
二、选择Object还是Map
对于大多数Web开发任务,选择Object还是Map主要取决于个人偏好,影响有限。然而,对于关注内存消耗和运行性能的开发者而言,对象与映射之间确实存在显著差异。
1.内存占用
Object和Map的工程级实现在不同浏览器间存在差异,但存储单个键值对所占用的内存均随键数量线性增加。批量添加或删除键值对的性能则取决于各浏览器对该类型的内存分配实现。尽管不同浏览器情况各异,但在给定固定内存大小的前提下,Map大约能比Object多存储50%的键值对。
2.插入性能
向Object和Map中插入新键值对的开销大致相当,但Map在所有浏览器中的插入速度通常略快。对于这两种类型,插入速度并不随键值对数量线性增加。若代码涉及大量插入操作,Map的性能优势更为明显。
3.查找速度
与插入不同,从大型Object和Map中查找键值对的性能差异极小;但若仅包含少量键值对,Object有时速度更快。当将Object当作数组使用(例如使用连续整数作为属性)时,浏览器引擎可进行优化,采用更高效的内存布局,而Map无法享受此优化。对于这两种类型,查找速度不随键值对数量线性增加。若代码涉及大量查找操作,某些情况下选择Object可能更优。
4.删除性能
使用delete删除Object属性的性能一直备受争议,目前在许多浏览器中依然如此。因此,出现了一些伪删除对象属性的操作,如将属性值设为undefined或null,但这往往是一种不理想的折中方案。对于大多数浏览器引擎而言,Map的delete()操作比插入和查找更快。若代码涉及大量删除操作,毫无疑问应选择Map。
三、weakMap
ECMAScript 6新增的“弱映射”(WeakMap)是一种新的集合类型,为JavaScript带来了增强的键值对存储机制。WeakMap是Map的“兄弟”类型,其API也是Map的子集。WeakMap中的“weak”(弱)描述了JavaScript垃圾回收程序对待弱映射中键的方式。
weakcMap 的弱 WeakMap中“weak”表示弱映射的键是“弱弱地拿着”的。意思就是,这些键不属于正式的引用,不会阻止垃圾回收,当浏览器需要回收内存时这些键是可能会被回收的。但要注意的是,弱映射中值的引用可不是“弱弱地拿着”的。只要键存在,键/值对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。
//会被回收
const wm = new WeakMap();
wm.set({}, "val");
//不会被回收
const wm2 = new WeakMap();
const container = {
key: {}
};
wm2.set(container.key, "val");
function removeReference() {
container.key = null;
}
在vm中,set()方法初始化了一个新对象并将它用作一个字符串的键。因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象键就会被当作垃圾回收。然后,这个键/值对就从弱映射中消失了,使其成为一个空映射。在这个例子中,因为值也没有被引用,所以这对键/值被破坏以后,值本身也会成为垃圾回收的目标。 而在vm1中,container对象维护着一个对弱映射键的引用,因此这个对象键不会成为垃圾回收的目标。不过,如果调用了removeReference(),就会摧毁键对象的最后一个引用,垃圾回收程序就可以把这个键/值对清理掉。
weakMap使用 WeakMap的初始化与map并没有什么太大的差别,需要注意的是weakMap只能使用object类型的键,这与weakMap的作用是息息相关的
const key1 = {id: 1},
key2 = {id: 2},
key3 = {id: 3};
// 使用嵌套数组初始化弱映射
const wm1 = new WeakMap([
[key1, "val1"],
[key2, "val2"],
[key3, "val3"]
]);
alert(wm.get(key1)); // val1
alert(wm.get(key2)); // val2
alert(wm.get(key3)); // val3
// 原始值可以先包装成对象再用作键
const stringKey = new String("key1");
const wm3 = new WeakMap([
stringKey, "val1"
]);
alert(wm3.get(stringKey)); // "val1"
四、使用weakMap的场景
WeakMap实例与现有JavaScript对象有着很大不同,可能一时不容易说清楚应该怎么使用它。这个问题没有唯一的答案,但已经出现了很多相关策略。
DOM节点元数据 因为WeakMap实例不会妨碍垃圾回收,所以非常适合保存关联元数据。如以下代码所示,下面的例子使用的是WeakMap,当节点从DOM树中被删除后,垃圾回收程序就可以立即释放其内存(假设没有其他地方引用这个对象):
const wm = new WeakMap();
const loginButton = document.querySelector('#login');
// 给这个节点关联一些元数据
wm.set(loginButton, {disabled: true});
总结
以上就是object、map、weakmap的相关使用和区别了。其实在大多数情况下,object和map使用是没有什么区别的,但是如果你需要大量的插入和查找删除,或者需要使用对象作为键值的话,使用map是比较优的选择。另外weakMap在使用的对象可能会被动态删除的情况下,比map具有优化内存的优势。
以上就是JavaScript中Object、map、weakmap的区别分析的详细内容,更多关于JavaScript中Object、map、weakmap区别的资料请关注本站其它相关文章!