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
46k21 gold badges167 silver badges290 bronze badges
asked Mar 10, 2015 at 15:00
13
  • non-string keys is a big one, if not the main reason to use Sets... Commented Mar 10, 2015 at 15:02
  • @dandavis: you mean the complete absence of keys in a Set, like jfriend00 is pointing out below? Commented Mar 10, 2015 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. Commented Mar 10, 2015 at 15:13
  • 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. Commented Mar 10, 2015 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. Commented Mar 10, 2015 at 20:15

3 Answers 3

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.

answered Mar 10, 2015 at 15:03
Sign up to request clarification or add additional context in comments.

12 Comments

how do you access a given element in a Set ?
@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: has() does not return the element, it returns a boolean indicating whether the element was/was not found
@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: right... Rephrasing my question... how can I loop over the elements of a Set ?
|
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.

5 Comments

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: danml.com/js/set.js
@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.
@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.
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 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.
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
answered Mar 10, 2015 at 15:08

7 Comments

@dandavis plain object can not take duplicate keys, in sets there is no need for key
To be added as a difference... +1
color me confused: if "Sets don't take duplicates" and "plain object can not take duplicate keys", what are you claiming the difference is?
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...)
@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
|

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.