×

ES6 集合:解析下如何使用 Map、Set、WeakMap、WeakSet

作者:Terry2024.09.10来源:Web前端之家浏览:163评论:0
关键词:jsJavascriptPython

ES6 集合:解析下如何使用 Map、Set、WeakMap、WeakSet

大多数主流编程语言都有几种类型的数据集合。Python 有列表、元组和字典。Java 有列表、集合、映射和队列。Ruby 有哈希和数组。到目前为止,JavaScript 只有数组。对象和数组是 JavaScript 的主力。ES6 引入了四种新的数据结构,将为该语言增添功能和表现力:Map、Set、WeakMap、WeakSet。

搜索 JavaScript HashMap

HashMap、字典和哈希是各种编程语言存储键/值对的几种方式,这些数据结构针对快速检索进行了优化。

在 ES5 中,JavaScript 对象(只是具有键和值的属性的任意集合)可以模拟哈希,但使用对象作为哈希有几个缺点。

缺点 1:ES5 中的键必须是字符串

JavaScript 对象属性的键必须是字符串,这限制了它们作为不同数据类型的键/值对集合的能力。当然,你可以将其他数据类型强制/字符串化为字符串,但这会增加额外的工作。

缺点 2:对象本质上不是可迭代的

对象并非设计为集合使用,因此没有有效的方法来确定对象有多少个属性。(例如,请参阅Object.keys 很慢)。当您循环遍历对象的属性时,您还会获取其原型属性。您可以将属性添加iterable到所有对象,但并非所有对象都应用作集合。您可以使用循环for … inhasOwnProperty()方法,但这只是一种解决方法。当您循环遍历对象的属性时,不一定按照插入顺序检索属性。

缺点 3:内置方法冲突带来的挑战

对象具有内置方法,如constructortoStringvalueOf。如果将其中一个方法添加为属性,则可能会导致冲突。您可以使用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 还引入了两个新的弱集合,分别称为WeakMapWeakSet。这些 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访问。privateDataWeakMapPerson

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 中的所有条目。这些集合没有clearMap 和 Set 中提供的方法。这是因为 WeakMap 和 WeakSet 旨在在对键进行垃圾回收时自动清除其条目。

我能获取 WeakMap 或 WeakSet 的大小吗?

不可以,您无法获取 WeakMap 或 WeakSet 的大小。这些集合没有sizeMap 和 Set 中可用的属性。这是因为 WeakMap 或 WeakSet 的大小可能随时因垃圾回收而发生变化。

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://jiangweishan.com/article/webjsusd823048.html

网友评论文明上网理性发言 已有0人参与

发表评论: