javascript 的 Set 和普通的普通对象有什么区别

What are the differences between javascript's Set and a regular plain object?

本文关键字:什么 区别 对象 javascript Set      更新时间:2023-09-26

在试图解决这个问题的过程中,我了解到javascript的Set对象的存在。然后我检查了 MDN 的 Set 文档,我在这里问的问题立即弹出。

这场辩论始于上述问题的回答评论。我发现这个线程也有助于辩论。

到目前为止,我自己可以从辩论中得出的结论是:

  • Set 在其 API 中提供了一个"清除"方法,这是一个不错的功能。

  • Set 可确保保留添加元素的顺序。普通对象则不然。

如果有的话,谁能提供明确的结论?也许我不应该比较两者...但我仍然认为这是一个合理的问题,并且其他人在了解 Set 后很可能会问同样的问题。

无论如何,最初促使我提出这个问题的是编辑 MDN 的 Set 规范,添加此处获得的结论。但是在这里开发线程之后,我认为我不应该这样做,也许 Set 的规范确实不应该提到"普通对象最终提供类似功能怎么办"的事情。至少问自己这个问题的人仍然可以在SO中找到它,并享受这里添加的见解/贡献。

首先,这里有一篇带有代码的文章,展示了如何开始使用普通的 Javascript 对象来实现类似集合的行为(有一些限制)。

而且,这里有一组对象(在 ES5 中工作),用于获得更多类似集合的行为。

并且,这里是在 ES5 中实现的 Set 对象的部分 ES6 填充。

如果你研究这些代码中的任何一个,你会发现一个普通的Javascript对象在许多方面远远低于ES6 Set,需要大量的额外编码才能解决。


以下是其中的几个问题:

  1. 将对象分配为键将无法正常工作,因为所有对象都转换为字符串键 "[object Object]"因此所有对象在类似集合的 Javascript 对象中都具有相同的键。

  2. 数字分配为键将转换为字符串表示形式,因此像 4"4" 这样的键将发生冲突。

ES6 Set 对象没有这些限制。 以下是对这两个问题的更多讨论:

如果你看一下上一篇文章的这个答案和该答案中的代码,你可以看到底层的Javascript对象如何被用作类似集合行为的查找机制。 但是,如果没有许多其他编码,它有一些限制,因为Javascript对象需要一个字符串作为查找键。 ES6 集则不然。 因此,开箱即用的 ES6 Set 支持对象作为元素。 使用 Javascript 对象作为穷人的集合不支持对象作为集合中的元素。

当您想要将对象添加到基于对象的纯 Javascript 集合中时,这将成为最重要的。

// create a set-like object
var mySet = {};
// create an object and put it in the set
var myObj = {greeting: "hello"};
mySet[myObj] = true;

因为 Javascript 对象需要一个字符串键,所以它会调用myObj.toString()来获取这样的键。 如果没有toString()方法的自定义覆盖,它将显示为"[对象对象]",这根本不是您想要的。 有关演示,请参见此处。 它似乎适用于一个对象,但是一旦集合中有多个对象或为另一个对象设置集合,它根本不起作用,因为所有对象都将使用相同的键进行索引。

另一方面,使用实际的 ES6 集,它原生地接受并处理对象就好了——你不必做任何特别的事情。

如果你想了解如何使用纯 Javascript 对象作为查找机制尽可能接近地模仿 ES6 集,请阅读此答案。 更多信息位于 github 上,您可以在其中查看如何使常规的 Javascript 基于对象的集实现支持使用此 ObjectSet 实现的 Javascript 对象。 这里甚至还有一个ES6 Set polyfill,它使用底层Javacript对象作为存储机制。


第二个问题出现在基于Javascript对象的集合实现中,这也是因为字符串键要求。 如果这样做:

var mySet = {};
mySet[4] = true;
mySet["4"] = true;

您最终只会得到套装中的一件物品。 这是因为mySet[4] = true;4转换为字符串"4"以用作键。 如果你只在集合中使用字符串或只使用集合中的数字,那么这很容易解决,但是如果你在集合中同时有字符串和数字,那么 javascript 对象库集实现不会区分4"4"因此不会将它们视为集合中的单独项目。 ES6确实做出了这种区分。

同样,可以使用更多代码解决此问题。 如果您自己手动执行toString()转换以创建键,则可以在键值前面附加一些类型信息,以便在上面的示例中,数字4的键变为"num_4",而不仅仅是"4"。 为了防止与字符串"num_4"发生冲突,还必须对字符串类型执行相同的操作。 数字并不是这个问题的唯一类型。 实际上,在此 ES6 Set polyfill 中,您可以看到此处生成的唯一键。 然后必须重新生成该唯一键,并用于在集合中进行任何查找。

Set 实例是对象。如果构造 Set,则可以自由地向其添加属性,就像 Date 或 Array 的实例一样:

var s = new Set();
s.name = "Herman";
s.shoeSize = 12;
console.dir(s); // just like an Object!
s.add(17);
if (s.has(17)) alert("yup!"); // yup!
if (17 in s) alert("yup!"); // no alert here!

Set 原型公开的行为提供了一种功能,该功能实际上与对象的性质无关,除了对对象的引用属于可与该 API 一起使用的值类型之外。通过该 API 在集合中添加或删除值不会影响 set 对象的普通属性。

如果希望仅使用 ES5 工具实现像 Set 工具这样的工具,则必须执行诸如维护值数组之类的操作,或者按值类型维护一组数组。.add().has()以及其他方法将不得不对该值列表施加集合成员资格的规则,可能通过搜索。可能有一些优化可以提高搜索效率,但 ES6 运行时具有优势,最值得注意的是运行时内部可以访问原始对象引用值和其他类似的东西。

集合不带重复 看这个小提琴

var mySet = new Set();
mySet.add(1);
mySet.add(5);
mySet.add("some text");

console.log('after adding duplicate  values ')
console.log('size='+mySet.size ) // size is 3

mySet.add("some text"); // adding duplicate value
console.log('after adding duplucate  value ')
console.log('size='+mySet.size ) // size is still 3