Let’s dive right into some code: Show
In this snippet we’re using two objects ( In JavaScript, objects were never intended to operate as keys in other objects. It isn’t the way the language is designed, and it’s impossible to use them this way out-of-the-box, as demonstrated by the previous code snippet. In the event that we do need this type of behavior, we can leverage a Map and be done with it:
You’re probably thinking, “Right. So why is the topic even open for discussion?” I’m glad you asked! Exploring alternate, unorthodox solutions to problems, even when they involve some practices that are not recommended for production code, can lead to unexpected learning and insight. There is a time and place for asking the questions “What if?” and “If so, how?” This is why we are here. What if we could use objects as keys? How might it work? In this post we will dig into this idea of using objects as keys without using a If you’re ready to learn more about JavaScript, let’s get started! Object PropertiesPerhaps the simplest thing you can do to an object is to give it a property with some value. As with anything in software development, there are a number of ways do so. You can declare initial properties when you create an object:
Or, you can intialize properties after object creation using the assignment operator:
And a third way would be to call Object.defineProperty or Reflect.defineProperty, passing the object, a property name, and a property descriptor:
In all of these cases, we would say that the string
Key Types and Automatic CoercionWhile a property’s value can be any type, its key must be one of only two types: a string or a symbol. When using any other key type, the JavaScript runtime will first try to coerce, or force, the key to a string before using it as a property key:
As you can see, when we use the number When a key is not a string and cannot be coerced to a string, the JS runtime will throw a good ole By default, an object’s
prototype points to the global
This explains why the first code snippet did not work as expected. Overriding the Default Coercion BehaviorBecause the JS runtime coerces objects to strings when they are used as keys in other objects, we need every unique object to be coerced to a unique string (instead of being coerced to the default
All of these approaches enable us to override object-to-primitive coercion behavior on individual objects, but we still don’t quite have what we need. Overriding Coercion For All ObjectsInstead of overriding the behavior on individual objects, we want all objects to inherit the overridden behavior by default. We can then create objects with object literal syntax and use those objects as keys without having to make changes to the object or its prototype chain. To that end, let’s define
As you can see, the Note: In the introduction to this post, I mentioned that our solution would incorporate practices that are not recommended for production code, and I was referring specifically to this. I don’t recommend making changes to The next step is the fun part! Generating Unique IDsOur When I first tried to solve this problem, my initial thought was that these IDs could be derived simply by “stringifying” the objects:
This solution indeed works for some use cases:
But it has the following major limitations:
We need
something much better. Instead of trying to derive the ID from an object’s contents, we can assign an ID to an object the first time that Let’s start by assigning the same ID to every object that
Notice a few things about these changes:
Let’s see what happens when our new function is invoked:
It works, but we’re going to want to find a better way to associate the ID with the object. We’ll look at why and how in a moment. First, let’s start assigning unique IDs! We can use an integer for the object ID and use a global variable to track what the next object ID will be. Each time we assign an object ID, we increment the “global ID counter”,
which we’ll name
The Let’s see how things are looking by running our first code snippet again:
It works! Even with this simple
In my console the assigned IDs ended up being We’ll make three improvements before calling it a day. Hiding the Object ID From EnumerationFirst, it’s not ideal that an object’s ID is stored as a normal property on the object. The ID will show up when enumerating the object’s keys and will, for instance, get copied to another object when spreading:
We’re now in a situation where two objects have the same ID. According to our
To fix this, we need
However, the whole point of this post is to mimic the behavior of
We actually don’t have to specify Now that our ID is non-enumerable, it’s much more invisible than it was before and won’t get copied to other objects:
There are still ways to see the ID property, and without using a
Nevertheless, we’ve hidden the ID property well enough for most use cases. Let’s move on to the second improvement. There is another
problem with the ID property that we need to fix: we’ve made it impossible for any other code to utilize an
The assignment of a new value to the
Because we did not include
We could make The
issue we’re facing isn’t that other code cannot assign to the
Here’s the updated code, adjusted to use a symbol for the object ID:
With this change, other parts of the codebase and
even other libraries are free to use any object property name without risking a collision with our ID property. Also, the ID property will now be returned from
Giving our symbol a “description” can help with debugging without impacting the behavior or uniqueness of the symbol:
Now that our object ID property is safe from being seen or altered by other code, let’s move on to the third improvement. Mitigating Collisions With Non-Object KeysThe IDs produced by
The code doesn’t behave as expected because Object ID PrefixWe can make our object IDs more globally unique by prefixing them with an obscure string, such as
Running the last code snippet now produces the expected results because The ID prefix is a solid solution and suffices for most use cases. However, while this solution significantly reduces the probability of collisions, it doesn’t eliminate the issue entirely. Some of you may already know where this is going! Object ID SymbolsInstead of using a global ID counter (
By using symbols, we have handed off to the browser the responsibility of creating unique keys. These keys will be unique across the space of all primitives coerced from JavaScript values. Unfortunately, there is one major caveat to this approach: property symbols are not included in the return value of
For this reason, the ID prefix approach may be superior. And that’s all! It took us a number of iterations to get here, but we landed on a simple The Journey Is More Important Than The DestinationWe can learn a lot from delving into an unconventional approach to solving a problem. Even though the final code for our object keying system shouldn’t be used in production, I hope you learned from the journey we took to build it. We discussed a number of ideas and language features. We saw problems with our solution and made improvements until we ended up with something robust and functional. Let’s revisit the highlights:
One Last ThingThe polyfill library
core-js is commonly used when developing for browsers that do not natively support certain standard JavaScript features. Not surprisingly, it contains polyfills for That’s All!We covered a lot of ground in this post. If you made it all the way through, thanks for reading! I sincerely hope you learned a thing or two about JavaScript that you didn’t know before. Happy coding! Can an object be a key in a JavaScript map?Introduction to JavaScript Map object
An object always has a default key like the prototype. A key of an object must be a string or a symbol, you cannot use an object as a key. An object does not have a property that represents the size of the map.
Can key in JavaScript object be a number?Each key in your JavaScript object must be a string, symbol, or number.
What can be a key in JavaScript?JavaScript object property names (keys) can only be strings or Symbols — all keys in the square bracket notation are converted to strings unless they are Symbols.
Can an object KEY be a function?The Object keys() function returns the array whose elements are strings corresponding to the enumerable properties found directly upon the object. An ordering of the properties is the same as that given by an object manually in the loop is applied to the properties.
|