大多数主流编程语言都有几种类型的数据集合。Python 有列表、元组和字典。Java 有列表、集合、映射和队列。Ruby 有哈希和数组。到目前为止,JavaScript 只有数组。对象和数组是 JavaScript 的主力。ES6 引入了四种新的数据结构,将为该语言增添功能和表现力:Map、Set、WeakMap、WeakSet。
搜索 JavaScript HashMap
HashMap、字典和哈希是各种编程语言存储键/值对的几种方式,这些数据结构针对快速检索进行了优化。
在 ES5 中,JavaScript 对象(只是具有键和值的属性的任意集合)可以模拟哈希,但使用对象作为哈希有几个缺点。
缺点 1:ES5 中的键必须是字符串
JavaScript 对象属性的键必须是字符串,这限制了它们作为不同数据类型的键/值对集合的能力。当然,你可以将其他数据类型强制/字符串化为字符串,但这会增加额外的工作。
缺点 2:对象本质上不是可迭代的
对象并非设计为集合使用,因此没有有效的方法来确定对象有多少个属性。(例如,请参阅Object.keys 很慢)。当您循环遍历对象的属性时,您还会获取其原型属性。您可以将属性添加iterable
到所有对象,但并非所有对象都应用作集合。您可以使用循环for … in
和hasOwnProperty()
方法,但这只是一种解决方法。当您循环遍历对象的属性时,不一定按照插入顺序检索属性。
缺点 3:内置方法冲突带来的挑战
对象具有内置方法,如constructor
、toString
和valueOf
。如果将其中一个方法添加为属性,则可能会导致冲突。您可以使用Object.create(null)
创建一个裸对象(不从 继承object.prototype
),但同样,这只是一种解决方法。
ES6 包含新的集合数据类型,因此不再需要使用对象并忍受其缺点。
使用 ES6 Map 集合
Map
是我们将要研究的第一个数据结构/集合。映射是任何类型的键和值的集合。创建新的映射、添加/删除值、循环键/值并高效确定其大小都很容易。以下是关键方法:
创建地图并使用常用方法
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 集合
集合是有序的值列表,不包含重复项。集合不像数组那样被索引,而是使用键来访问。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、内存和垃圾收集
JavaScript 垃圾收集是一种内存管理形式,通过这种方式,不再引用的对象会被自动删除并回收其资源。
Map
并且Set
对对象的引用是强持有的,不允许进行垃圾回收。如果映射/集合引用不再需要的大型对象(例如已从 DOM 中删除的 DOM 元素),则这可能会变得昂贵。
为了解决这个问题,ES6 还引入了两个新的弱集合,分别称为WeakMap
和WeakSet
。这些 ES6 集合之所以“弱”,是因为它们允许将不再需要的对象从内存中清除。
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
使用案例
WeakMaps有几种常见的用例。它们可用于保持对象的私有数据不被泄露,也可用于跟踪 DOM 节点/对象。
私有数据用例
以下示例来自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 节点用例
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 集合
Maps 和 Sets 是 ES6 中新的键/值对集合。尽管如此,JavaScript 对象在许多情况下仍可用作集合。除非情况需要,否则无需切换到新的 ES6 集合。
MDN 有一个很好的问题列表来确定何时使用对象或键集合:
密钥通常在运行时才为人所知,您需要动态地查找它们吗?
所有值是否具有相同的类型,并且可以互换使用?
您需要非字符串的键吗?
键值对是否经常添加或删除?
您是否有任意数量的(容易改变的)键值对?
该集合是迭代的吗?
新的 ES6 集合让 JavaScript 更加易用
JavaScript 集合以前非常有限,但 ES6 已经解决了这个问题。这些新的 ES6 集合将为该语言增添功能和灵活性,并简化采用它们的 JavaScript 开发人员的任务。
关于 ES6 集合的常见问题 (FAQ):Map、Set、WeakMap、WeakSet
JavaScript ES6 中的 Map 和 WeakMap 的主要区别是什么?
在 JavaScript ES6 中,Map 和 WeakMap 都用于存储键值对。但是,它们之间存在一些显著差异。首先,在 Map 中,键可以是任何类型,而在 WeakMap 中,键必须是对象。其次,Map 具有 size 属性,可让您检查键值对的数量,但 WeakMap 没有此属性。最后,Map 持有对键对象的强引用,这意味着只要 Map 存在,它们就不会被垃圾回收。另一方面,WeakMap 持有对键对象的弱引用,这意味着如果没有其他对该对象的引用,它们就会被垃圾回收。
如何在 JavaScript ES6 中迭代 WeakMap 或 WeakSet?
与 Map 和 Set 不同,WeakMap 和 WeakSet 没有迭代元素的方法。这是因为它们被设计为保存对其键 (WeakMap) 或值 (WeakSet) 的弱引用,这意味着这些引用随时可能被垃圾回收。因此,无法保证当您尝试迭代元素时该元素仍然存在。如果您需要迭代集合,则应改用 Map 或 Set。
我可以使用原始数据类型作为 WeakMap 或 WeakSet 中的键吗?
不可以,您不能将原始数据类型用作 WeakMap 或 WeakSet 中的键。这些集合中的键必须是对象。这是因为 WeakMap 和 WeakSet 持有对其键的弱引用,这意味着如果没有其他引用这些键,这些键可能会被垃圾回收。原始数据类型(例如数字和字符串)的垃圾回收方式与对象不同,因此它们不能用作这些集合中的键。
为什么我要使用 WeakMap 或 WeakSet 而不是 Map 或 Set?
WeakMap 和 WeakSet 具有一些独特的功能,在某些情况下,这些功能比 Map 或 Set 更合适。由于它们持有对其键 (WeakMap) 或值 (WeakSet) 的弱引用,因此当它们不再使用时,它们可以被垃圾回收。如果您想要将其他数据与对象关联,但又不想阻止对象在不再需要时被垃圾回收,那么这会很有用。此外,由于 WeakMap 和 WeakSet 没有迭代其元素的方法,因此它们可以为它们存储的数据提供一定程度的隐私。
当 WeakMap 或 WeakSet 中的键被垃圾收集时会发生什么?
当 WeakMap 或 WeakSet 中的键被垃圾回收时,集合中的相应条目将自动移除。这是因为这些集合持有对其键的弱引用,这意味着当键不再使用时,它们可以被垃圾回收。此功能对于管理 JavaScript 应用程序中的内存非常有用,因为它可以确保与不再使用的对象相关联的数据也被清理。
我可以使用 WeakMap 或 WeakSet 来存储临时数据吗?
是的,WeakMap 和 WeakSet 非常适合存储临时数据。因为它们持有对键 (WeakMap) 或值 (WeakSet) 的弱引用,所以当它们不再使用时,它们可以被垃圾回收。这意味着存储在这些集合中的数据也会在键被垃圾回收时被清理。这对于存储只需要短时间的数据非常有用,因为您不必担心手动清理它。
如何检查 WeakMap 或 WeakSet 是否包含某个键或值?
您可以使用该has
方法检查 WeakMap 或 WeakSet 是否包含某个键。此方法返回一个布尔值,指示该键是否存在于集合中。但是,请记住,您不能使用此方法来检查 WeakSet 中的某个值,因为此集合中的值是不可访问的。
我可以从 WeakMap 或 WeakSet 中删除一个条目吗?
是的,您可以使用该方法从 WeakMap 中删除条目delete
。此方法会删除与给定键关联的条目,并返回一个布尔值,指示该键是否存在于集合中。但是,您无法从 WeakSet 中删除条目,因为此集合没有方法delete
。
我可以清除 WeakMap 或 WeakSet 中的所有条目吗?
不可以,您无法清除 WeakMap 或 WeakSet 中的所有条目。这些集合没有clear
Map 和 Set 中提供的方法。这是因为 WeakMap 和 WeakSet 旨在在对键进行垃圾回收时自动清除其条目。
我能获取 WeakMap 或 WeakSet 的大小吗?
不可以,您无法获取 WeakMap 或 WeakSet 的大小。这些集合没有size
Map 和 Set 中可用的属性。这是因为 WeakMap 或 WeakSet 的大小可能随时因垃圾回收而发生变化。
网友评论文明上网理性发言 已有0人参与
发表评论: