4

In trying to solve this question, I learned about the existence of javascript's Set object. I then checked MDN's Set documentation, and the question I am asking here immediately popped up.

This debate started in the aforementioned question's answers comments. I found that this thread also contributes to the debate.

So far, what I myself could make out of the debate is:

  • Set provides a "clear" method in its API, which is a nice-to-have feature

  • Set ensures the order of the elements added is kept. A plain object does not.

Can anyone provide a definite conclusion, if any? Perhaps I should not be comparing the two... but I still think it is a legitimate question and that it is pretty likely that others will ask the same upon learning about Set.

In any case, what initially propelled me to ask this was to edit MDN's Set specs adding the conclusion obtained here. But after the development of the thread here I think I should not be doing it, and perhaps Set's specs should not indeed mention a thing about "what about plain objects ultimately providing similar functionality". At least whoever asks himself the question may still find it here in SO and enjoy the insights/contributions added here.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Veverke
  • 9,208
  • 4
  • 51
  • 95
  • non-string keys is a big one, if not the main reason to use Sets... – dandavis Mar 10 '15 at 15:02
  • @dandavis: you mean the complete absence of keys in a Set, like jfriend00 is pointing out below? – Veverke Mar 10 '15 at 15:07
  • Keep in mind that, in addition to the functionality provided by the API on `Set.prototype`, a Set instance *is* a plain object. – Pointy Mar 10 '15 at 15:13
  • yeah, that's closer what i meant. the limit with using objects as sets is expensive lookup and string keys, neither of which are a problem with Sets. Sets are more comparrable to Arrays (w/ .indexOf), it's Maps that match up more with plain Objects. – dandavis Mar 10 '15 at 15:15
  • 1
    @Veverke - I've added a lot more to my answer to point out two limitations of using a Javascript object as your lookup mechanism for a poor man's set. I've also linked in several polyfill implementations that show you all the extra work required to make a Javascript object come close to doing what an ES6 Set does. – jfriend00 Mar 10 '15 at 20:14
  • 1
    FYI, it isn't really StackOverflow's policiy for you collect info from the answers and put it in your question. Your question should be the question and the answers should be the answers. – jfriend00 Mar 10 '15 at 20:15
  • here's a 5-line Set implementation i wrote back in the day using Arrays: http://danml.com/js/set.js . – dandavis Mar 10 '15 at 21:33
  • @jfriend00: besides that being the policy, I find reason in that. Please understand that I just wanted to summarize the outcome of the discussion (which had gone extended) so that the thread is optimally useful - but, I did not give it a thought, and I agree with you. Could I summarize everything, at the end (if no one else does) and post an answer with the outcome/conclusions ? – Veverke Mar 11 '15 at 10:11
  • @Veverke - it sounds like you want more of a community wiki kind of answer - I'm not really familiar with how that is supposed to work - though have occasionally seen it. Some description of it [here](http://meta.stackexchange.com/questions/11740/what-are-community-wiki-posts). Some people who have invested in answers might not appreciate you just copying their content into a new answer though. I'm not sure what you're trying to achieve or what the right way to get there is. I'm more familiar with the question/answer mode. Keep in mind that not everything fits in the framework here too. – jfriend00 Mar 11 '15 at 10:17
  • @jfriend00: thanks for the efforts of making your points clear, now I see there is a lot more at stake that I did not think of in the first place. Ultimately - I wanted to collect the differences between a Set and a plain object - to add it to MDN's Set documentation - because I thought... (perhaps here is my problem, although good-intended) many people would ask themselves this very question (that's what happened to me after staring at the definitions of a set for the 1st time). – Veverke Mar 11 '15 at 10:24
  • @Veverke - since I implemented multiple versions of Set objects (one that had my own API and then one that has the ES6 API when it came out) and posted links to the code for all those, I thought that would be useful to show what is involved in making a Set out of using a Javascript object as the lookup mechanism. I tried to describe in my answer some of the things I had to design around to solve problems. I thought that's what you were asking for. Things like the ability of a set to hold Objects, the ability to hold non string types and not conflict with other things, etc... – jfriend00 Mar 11 '15 at 10:27
  • I will go over all the answers again, much info has been added here... thanks again for all the contributions. – Veverke Mar 11 '15 at 10:41
  • @jfriend00: first thing, I edit the question's description. – Veverke Mar 11 '15 at 11:07

3 Answers3

3

First off, here's a post with code that shows how to start to use a plain Javascript object for set-like behavior (with some limitations).

And, here's a set of objects (that work in ES5) for getting lots more set-like behavior.

And, here a partial ES6 polyfill for the Set object implemented in ES5.

If you study any of this code, you will see that a plain Javascript object falls far short of an ES6 Set in many ways that require significant additional coding to work around.


Here are a couple of those issues:

  1. Assigning an object as a key will not work properly because all objects convert to a string key of "[object Object]" so all objects would have the same key in your set-like Javascript object.

  2. Assigning a number as a key will convert to the string representation so keys like 4 and "4" will conflict.

The ES6 Set object does not have these limitations. Here's more discussion of these two issues:

If you look at this answer to a prior post and the code in that answer, you can see how an underlying Javascript object can be used as the lookup mechanism for set-like behavior. Without lots of other coding though, it has some limitations because a Javascript object requires a string as the lookup key. An ES6 Set does not. So, out of the box the ES6 Set supports objects as elements. Using a Javascript object as a poor man's set does not support objects as elements in the set.

This becomes the most material when you want to add an object to the plain Javascript object-based set.

// create a set-like object
var mySet = {};

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

Because a Javascript object requires a string key, it will call myObj.toString() to get such a key. Without a custom override for the toString() method, that will come out as "[object Object]" which is not at all what you want. See here for a demo. It will appear to work for one object, but as soon as you have more than one object in the set or set the set for a different object, it won't work at all because all objects would get indexed with the same key.

With an actual ES6 set, on the other hand, it natively accepts and works with objects just fine - you don't have to do anything special.

If you want to see how you can mimic an ES6 set as closely as possible using a plain Javascript object as the lookup mechanism, please read this answer. Further info is located on github where you can see what as to be done to make a regular Javascript object-based set implementation support Javascript objects with this ObjectSet implementation. There's even an ES6 Set polyfill here that uses an underlying Javacript object as it's storage mechanism.


A second issue arises with Javascript object based set implementations which is also because of the string key requirement. If you do this:

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

You will end up with only one item in the set. This is because mySet[4] = true; converts the 4 to a string "4" to use as the key. If you are only using strings in your set or only using numbers in the set, then this can be easily worked around, but if you have both strings and numbers in the set, then a javascript object-base set implementation does not distinguish between 4 and "4" so does not treat them as separate items in the set. An ES6 does make this distinction.

Again, it would be possible to work around this with more code. If you manually do the toString() conversion yourself to create the key, one can preprend to the key value some type information such that in the above example, the key for the number 4 becomes "num_4" instead of just "4". To prevent collisions with a string '"num_4"`, you have to also do the same for string types. And numbers aren't the only type with this issue. In fact, in this ES6 Set polyfill, you can see a unique key being generated here. That unique key then has to be regenerated and used to do any lookup in the set.

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • how do you access a given element in a Set ? – Veverke Mar 10 '15 at 15:06
  • @Veverke you use the `.has()` function to tell whether a value is in a particular set. Once you know that, you don't need to "access" the value *from* the set because you already have it. – Pointy Mar 10 '15 at 15:07
  • @Pointy: has() does not return the element, it returns a boolean indicating whether the element was/was not found – Veverke Mar 10 '15 at 15:09
  • @Veverke I know, but if you can call `.has()` then you **already have the value**. Really, your question is a little odd - what does "a given element" mean? If you mean "an element that satisfies a certain condition", then you have to iterate through the `.values()` and check each member. – Pointy Mar 10 '15 at 15:10
  • @Pointy: right... Rephrasing my question... how can I loop over the elements of a Set ? – Veverke Mar 10 '15 at 15:11
  • @Veverke with `Set.prototype.forEach()` – Pointy Mar 10 '15 at 15:25
  • Regarding the answer itself - it is not clear how relevant this is (and is perhaps related to the comment discussion under NoDownVotesPlz's answer) - are you saying that a Set can keep objects from all sorts of types - but that plain objects can keep KEYS as strings only ? Why would this be a problem ? You would just have to work against the property value instead of the keys – Veverke Mar 10 '15 at 15:30
  • yeah, Sets don't really compare to Objects, hence the confusion here. Sets are much more like Arrays than Objects, they even share the same methods (given has vs contains/indexOf)... – dandavis Mar 10 '15 at 15:46
  • @dandavis - one can implement set-like behavior using an object. See [here](http://stackoverflow.com/questions/7958292/mimicking-sets-in-javascript/7958422#7958422). – jfriend00 Mar 10 '15 at 18:27
  • Now that I'm back on my computer, I reworked my answer to explain better the limitation of using a plain Javascript object as a set-like object. – jfriend00 Mar 10 '15 at 19:52
  • @Veverke - this answer is exactly relevant to the question. The OP wants to know the differences between an ES6 Set and a plain object used like a Set. The ability to automatically index any type of object in an ES6 set is such a relevant difference. Without that, it takes custom coding to generate unique string keys for objects that don't automatically have a unique string representation and, in fact, that custom key has to be stored as a property on the object (thus even disturbing the object being put in the set). This is a very relevant difference. – jfriend00 Mar 10 '15 at 19:55
  • Added a second difference where the ES6 set can uniquely store `4` and "`4"` as separate items, but a Javascript object would not because it would string convert them both to the same key. – jfriend00 Mar 10 '15 at 20:12
2

Set instances are objects. If you construct a Set, you're free to add properties to it just like an instance of Date or 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!

The behaviors exposed by the Set prototype provide a functionality that really has nothing to do with the nature of objects, other than that references to objects are among the sorts of values that can be used with that API. Adding or removing values from a set via that API has no effect on the ordinary properties of the set object.

If one wished to implement a facility like the Set facility using only ES5 facilities, you'd have to do something like maintain an array of values, or a set of arrays by value type perhaps. The .add() and .has() and other methods would have to impose the rules of set membership over that list of values, probably by searching. There are probably some optimizations that could be done to make the searching more efficient, but the ES6 runtime has advantages, most notably that the runtime internals have access to raw object reference values and other such things.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • sorry, but this code is basically wrong. you're just using the Set as an Object, just like you can abuse Array with keys ala `[].name="joe"`... if you check set.size, it still says 0, and setting direct props instead of using add() provides none of the benefits of Sets discussed by others on this page... implementing Set in ES5 is actually really simple: http://danml.com/js/set.js – dandavis Mar 10 '15 at 21:38
  • @dandavis that's the point of the post - a set *is* an object. The second and third paragraphs are an attempt to explain that the set behaviors exposed in the API are a totally separate mechanism than the Map-like behavior of ordinary JavaScript objects. Basically I think that the original question really doesn't make much sense, or else has a trivial answer: a Set instance is different from a regular object because it inherits abilities from the Set prototype. Those abilities do not *deprive* a set of the abilities of ordinary objects. – Pointy Mar 11 '15 at 01:24
  • @dandavis your Set polyfill looks fine, except that you might want to handle the (probably not practically important) case of `NaN`. The mention of the "advantages" of the native runtime are basically that the native runtime has ways of doing better than a simple linear search for checking whether values are in the set or not. – Pointy Mar 11 '15 at 01:26
  • thanks for the info, much appreciated. my "polyfill" was little more than a one-sitting attempt a couple years ago to understand what a Set is; i learn best by building. That some can bring better perf is a testament to engineering and ingenuity. On a side, I had been 24 hours without sleep yesterday and it made it even more confusing. A simple "Objects have key:value pairs, Sets have just values" would probably have sufficed. – dandavis Mar 11 '15 at 11:55
  • @dandavis no problem. I hadn't looked much at the Set stuff until this question :) I went ahead and added a couple lines of the Set API to my sample code because you were right that it should be there to clearly illustrate what I was saying. – Pointy Mar 11 '15 at 13:40
1

Sets don't take duplicates see this fiddle

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
Naeem Shaikh
  • 15,331
  • 6
  • 50
  • 88
  • 1
    @dandavis plain object can not take duplicate `keys`, in sets there is no need for key – Naeem Shaikh Mar 10 '15 at 15:09
  • To be added as a difference... +1 – Veverke Mar 10 '15 at 15:15
  • color me confused: if "Sets don't take duplicates" and "plain object can not take duplicate keys", what are you claiming the difference is? – dandavis Mar 10 '15 at 15:16
  • let's put it this way: if element A is present at Set S, you can guarantee that a single instance of A exists in S. If p is a property and v is it's value in an object O, p is unique in O, but v has not to. If we are interested in the elements themselves, then a Set can give us that assurance. In an object, you must look at the properties, not at the values, for that. (not as clear as I wanted to...) – Veverke Mar 10 '15 at 15:22
  • @dandavis.. I respect what u say.. But according to me there is a difference when there is no key in the set, then u cant even have duplicate value, in case of plain object you can have same value on different keys – Naeem Shaikh Mar 10 '15 at 15:24
  • So consider you create keys named 0-10 all having same values.. But in set you put 10 same values without the key, and they are not duplicated – Naeem Shaikh Mar 10 '15 at 15:25
  • ok, i see your point, sorry to be a bummer. {a:1, b:1} doesn't apply to sets. i don't disagree at all, and it's worth noting. it's in the analogy to the object that i got hung-up on, specifically trying to model a Set with a plain object. – dandavis Mar 10 '15 at 15:43