大多数主流编程语言都有几种类型的数据集合。Python 有列表、元组和字典。Java 有列表、集合、映射和队列。Ruby 有哈希和数组。到目前为止,JavaScript 只有数组。对象和数组是 JavaScript 的主力。ES6 引入了四种新的数据结构,将为该语言增添功能和表现力:Map、Set、WeakMap、WeakSet。
搜索 JavaScript HashMap {#searchingforthejavascripthashmap}
HashMap、字典和哈希是各种编程语言存储键/值对的几种方式,这些数据结构针对快速检索进行了优化。
在 ES5 中,JavaScript 对象(只是具有键和值的属性的任意集合)可以模拟哈希,但使用对象作为哈希有几个缺点。
缺点 1:ES5 中的键必须是字符串 {#downside1keysmustbestringsines5}
JavaScript 对象属性的键必须是字符串,这限制了它们作为不同数据类型的键/值对集合的能力。当然,你可以将其他数据类型强制/字符串化为字符串,但这会增加额外的工作。
缺点 2:对象本质上不是可迭代的 {#downside2objectsarenotinherentlyiterable}
对象并非设计为集合使用,因此没有有效的方法来确定对象有多少个属性。(例如,请参阅Object.keys 很慢)。当您循环遍历对象的属性时,您还会获取其原型属性。您可以将属性添加iterable
到所有对象,但并非所有对象都应用作集合。您可以使用循环for ... in
和hasOwnProperty()
方法,但这只是一种解决方法。当您循环遍历对象的属性时,不一定按照插入顺序检索属性。
缺点 3:内置方法冲突带来的挑战 {#downside3challengeswithbuiltinmethodcollisions}
对象具有内置方法,如constructor
、toString
和valueOf
。如果将其中一个方法添加为属性,则可能会导致冲突。您可以使用Object.create(null)
创建一个裸对象(不从 继承object.prototype
),但同样,这只是一种解决方法。
ES6 包含新的集合数据类型,因此不再需要使用对象并忍受其缺点。
使用 ES6 Map 集合 {#usinges6mapcollections}
Map
是我们将要研究的第一个数据结构/集合。映射是任何类型的键和值的集合。创建新的映射、添加/删除值、循环键/值并高效确定其大小都很容易。以下是关键方法:
创建地图并使用常用方法 {#creatingamapandusingcommonmethods}
const map = new Map(); // Create a new Map
map.set('hobby', 'cycling'); // Sets a key value pair
const foods = { dinner: 'Curry', lunch: 'Sandwich', breakfast: 'Eggs' }; // New Object
const normalfoods = {}; // New Object
map.set(normalfoods, foods); // Sets two objects as key value pair
for (const [key, value] of map) {
console.log(`${key} = ${value}`); // hobby = cycling [object Object] = [object Object]
}
map.forEach((value, key) => {
console.log(`${key} = ${value}`);
}, map); // hobby = cycling [object Object] = [object Object]
map.clear(); // Clears key value pairs
console.log(map.size === 0); // True
使用 Set 集合 {#usingthesetcollection}
集合是有序的值列表,不包含重复项。集合不像数组那样被索引,而是使用键来访问。Java 、Ruby、Python和许多其他语言中已经存在集合。ES6 集合与其他语言集合之间的一个区别是,ES6 中的顺序很重要(在许多其他语言中则不是这样)。以下是关键的集合方法:
const planetsOrderFromSun = new Set();
planetsOrderFromSun.add('Mercury');
planetsOrderFromSun.add('Venus').add('Earth').add('Mars'); // Chainable Method
console.log(planetsOrderFromSun.has('Earth')); // True
planetsOrderFromSun.delete('Mars');
console.log(planetsOrderFromSun.has('Mars')); // False
for (const x of planetsOrderFromSun) {
console.log(x); // Same order in as out - Mercury Venus Earth
}
console.log(planetsOrderFromSun.size); // 3
planetsOrderFromSun.add('Venus'); // Trying to add a duplicate
console.log(planetsOrderFromSun.size); // Still 3, Did not add the duplicate
planetsOrderFromSun.clear();
console.log(planetsOrderFromSun.size); // 0
WeakMap、内存和垃圾收集 {#weakcollectionsmemoryandgarbagecollections}
JavaScript 垃圾收集是一种内存管理形式,通过这种方式,不再引用的对象会被自动删除并回收其资源。
Map
并且Set
对对象的引用是强持有的,不允许进行垃圾回收。如果映射/集合引用不再需要的大型对象(例如已从 DOM 中删除的 DOM 元素),则这可能会变得昂贵。
为了解决这个问题,ES6 还引入了两个新的弱集合,分别称为WeakMap
和WeakSet
。这些 ES6 集合之所以"弱",是因为它们允许将不再需要的对象从内存中清除。
WeakMap {#weakmap}
WeakMap 是我们正在介绍的第三个新的 ES6 集合。WeakMaps
与正常的集合类似Maps
,尽管方法较少,并且有前面提到的与垃圾收集相关的差异。
const aboutAuthor = new WeakMap(); // Create New WeakMap
const currentAge = {}; // key must be an object
const currentCity = {}; // keys must be an object
aboutAuthor.set(currentAge, 30); // Set Key Values
aboutAuthor.set(currentCity, 'Denver'); // Key Values can be of different data types
console.log(aboutAuthor.has(currentCity)); // Test if WeakMap has a key
aboutAuthor.delete(currentAge); // Delete a key
使用案例 {#usecases}
WeakMaps有几种常见的用例。它们可用于保持对象的私有数据不被泄露,也可用于跟踪 DOM 节点/对象。
私有数据用例 {#privatedatausecase}
以下示例来自JavaScript 专家 Nicholas C. Zakas:
var Person = (function() {
var privateData = new WeakMap();
function Person(name) {
privateData.set(this, { name: name });
}
Person.prototype.getName = function() {
return privateData.get(this).name;
};
return Person;
}());
在此处使用WeakMap
可简化保持对象数据私有的过程。可以引用该对象,但如果没有特定实例,则不允许Person
访问。privateDataWeakMap``Person
DOM 节点用例 {#domnodesusecase}
Google Polymer 项目使用了WeakMaps
一段名为 PositionWalker 的代码。
PositionWalker 跟踪 DOM 子树中的位置,作为当前节点和该节点内的偏移量。
WeakMap 用于跟踪 DOM 节点的编辑、删除和更改:
_makeClone() {
this._containerClone = this.container.cloneNode(true);
this._cloneToNodes = new WeakMap();
this._nodesToClones = new WeakMap();
...
let n = this.container;
let c = this._containerClone;
// find the currentNode's clone
while (n !== null) {
if (n === this.currentNode) {
this._currentNodeClone = c;
}
this._cloneToNodes.set(c, n);
this._nodesToClones.set(n, c);
n = iterator.nextNode();
c = cloneIterator.nextNode();
}}
WeakSets
WeakSets
是集合,当不再需要其引用的对象时,其元素将被垃圾回收。WeakSets
不允许迭代。它们的用例相当有限(至少目前如此)。大多数早期采用者表示,WeakSets
可用于标记对象而不改变它们。ES6 -Features.org有一个从 WeakSet 添加和删除元素的示例,以跟踪对象是否已被标记:
let isMarked = new WeakSet()
let attachedData = new WeakMap()
export class Node {
constructor (id) { this.id = id }
mark () { isMarked.add(this) }
unmark () { isMarked.delete(this) }
marked () { return isMarked.has(this) }
set data (data) { attachedData.set(this, data) }
get data () { return attachedData.get(this) }
}
let foo = new Node("foo")
JSON.stringify(foo) === '{"id":"foo"}'
foo.mark()
foo.data = "bar"
foo.data === "bar"
JSON.stringify(foo) === '{"id":"foo"}'
isMarked.has(foo) === true
attachedData.has(foo) === true
foo = null /* remove only reference to foo */
attachedData.has(foo) === false
isMarked.has(foo) === false
映射所有事物?记录与 ES6 集合 {#mapallthingsrecordsvses6collections}
Maps 和 Sets 是 ES6 中新的键/值对集合。尽管如此,JavaScript 对象在许多情况下仍可用作集合。除非情况需要,否则无需切换到新的 ES6 集合。
MDN 有一个很好的问题列表来确定何时使用对象或键集合:
-
密钥通常在运行时才为人所知,您需要动态地查找它们吗?
-
所有值是否具有相同的类型,并且可以互换使用?
-
您需要非字符串的键吗?
-
键值对是否经常添加或删除?
-
您是否有任意数量的(容易改变的)键值对?
-
该集合是迭代的吗?
新的 ES6 集合让 JavaScript 更加易用 {#newes6collectionsyieldamoreusablejavascript}
JavaScript 集合以前非常有限,但 ES6 已经解决了这个问题。这些新的 ES6 集合将为该语言增添功能和灵活性,并简化采用它们的 JavaScript 开发人员的任务。
关于 ES6 集合的常见问题 (FAQ):Map、Set、WeakMap、WeakSet {#h-frequently-asked-questions-faqs-about-es-collections-map-set-weakmap-weakset}
JavaScript ES6 中的 Map 和 WeakMap 的主要区别是什么? {#faq-question-1346410000000}
在 JavaScript ES6 中,Map 和 WeakMap 都用于存储键值对。但是,它们之间存在一些显著差异。首先,在 Map 中,键可以是任何类型,而在 WeakMap 中,键必须是对象。其次,Map 具有 size 属性,可让您检查键值对的数量,但 WeakMap 没有此属性。最后,Map 持有对键对象的强引用,这意味着只要 Map 存在,它们就不会被垃圾回收。另一方面,WeakMap 持有对键对象的弱引用,这意味着如果没有其他对该对象的引用,它们就会被垃圾回收。
如何在 JavaScript ES6 中迭代 WeakMap 或 WeakSet? {#faq-question-1346410000001}
与 Map 和 Set 不同,WeakMap 和 WeakSet 没有迭代元素的方法。这是因为它们被设计为保存对其键 (WeakMap) 或值 (WeakSet) 的弱引用,这意味着这些引用随时可能被垃圾回收。因此,无法保证当您尝试迭代元素时该元素仍然存在。如果您需要迭代集合,则应改用 Map 或 Set。
我可以使用原始数据类型作为 WeakMap 或 WeakSet 中的键吗? {#faq-question-1346410000002}
不可以,您不能将原始数据类型用作 WeakMap 或 WeakSet 中的键。这些集合中的键必须是对象。这是因为 WeakMap 和 WeakSet 持有对其键的弱引用,这意味着如果没有其他引用这些键,这些键可能会被垃圾回收。原始数据类型(例如数字和字符串)的垃圾回收方式与对象不同,因此它们不能用作这些集合中的键。
为什么我要使用 WeakMap 或 WeakSet 而不是 Map 或 Set? {#faq-question-1346410000003}
WeakMap 和 WeakSet 具有一些独特的功能,在某些情况下,这些功能比 Map 或 Set 更合适。由于它们持有对其键 (WeakMap) 或值 (WeakSet) 的弱引用,因此当它们不再使用时,它们可以被垃圾回收。如果您想要将其他数据与对象关联,但又不想阻止对象在不再需要时被垃圾回收,那么这会很有用。此外,由于 WeakMap 和 WeakSet 没有迭代其元素的方法,因此它们可以为它们存储的数据提供一定程度的隐私。
当 WeakMap 或 WeakSet 中的键被垃圾收集时会发生什么? {#faq-question-1346410000004}
当 WeakMap 或 WeakSet 中的键被垃圾回收时,集合中的相应条目将自动移除。这是因为这些集合持有对其键的弱引用,这意味着当键不再使用时,它们可以被垃圾回收。此功能对于管理 JavaScript 应用程序中的内存非常有用,因为它可以确保与不再使用的对象相关联的数据也被清理。
我可以使用 WeakMap 或 WeakSet 来存储临时数据吗? {#faq-question-1346410000005}
是的,WeakMap 和 WeakSet 非常适合存储临时数据。因为它们持有对键 (WeakMap) 或值 (WeakSet) 的弱引用,所以当它们不再使用时,它们可以被垃圾回收。这意味着存储在这些集合中的数据也会在键被垃圾回收时被清理。这对于存储只需要短时间的数据非常有用,因为您不必担心手动清理它。
如何检查 WeakMap 或 WeakSet 是否包含某个键或值? {#faq-question-1346410000006}
您可以使用该has
方法检查 WeakMap 或 WeakSet 是否包含某个键。此方法返回一个布尔值,指示该键是否存在于集合中。但是,请记住,您不能使用此方法来检查 WeakSet 中的某个值,因为此集合中的值是不可访问的。
我可以从 WeakMap 或 WeakSet 中删除一个条目吗? {#faq-question-1346410000007}
是的,您可以使用该方法从 WeakMap 中删除条目delete
。此方法会删除与给定键关联的条目,并返回一个布尔值,指示该键是否存在于集合中。但是,您无法从 WeakSet 中删除条目,因为此集合没有方法delete
。
我可以清除 WeakMap 或 WeakSet 中的所有条目吗? {#faq-question-1346410000008}
不可以,您无法清除 WeakMap 或 WeakSet 中的所有条目。这些集合没有clear
Map 和 Set 中提供的方法。这是因为 WeakMap 和 WeakSet 旨在在对键进行垃圾回收时自动清除其条目。
我能获取 WeakMap 或 WeakSet 的大小吗? {#faq-question-1346410000009}
不可以,您无法获取 WeakMap 或 WeakSet 的大小。这些集合没有size
Map 和 Set 中可用的属性。这是因为 WeakMap 或 WeakSet 的大小可能随时因垃圾回收而发生变化。