As we already know, one of the differences between an array and object is:
"If you want to supply specific keys, the only choice is an object. If you don't care about the keys, an array it is" (Read more here)
Besides, according to MDN's documentation:
Arrays cannot use strings as element indexes (as in an associative array) but must use integers
However, I was surprised that:
> var array = ["hello"]; // Key is numeric index
> array["hi"] = "weird"; // Key is string
content structure looks like: ["hello", hi: "weird"]
The content structure of the array looks weird.
Even more, when I check the type of array it returns true
Array.isArray(array) // true
Questions:
This stumped me recently as well! It seemed to me that if JavaScript allows something like ['a', b: 'c']
, it must support associative arrays, so why all the redundancy?
Upon further research, I realized while this does look like an associative array or a JS object…
Array.isArray(array)
returned true.Here are the TL;DR versions of my answers to your questions. For more context and examples, keep scrolling…
If you require keys, and you also want to maintain the insertion order of your keys (since JS objects cannot reliably retain their property order), consider using a Map object-type instead.
Almost everything in JavaScript is some sort of object in one way or another, especially if you start exploring the __proto__
chain (MDN docs). In the same way, your array is an array, but it's also an object, just as all arrays are.
However, when you add a value to your array with a key like array["hi"] = "weird"
, you're not actually pushing a new value to the array, or even a key to the object for that matter in the usual JS objects kind of way, but rather, you are adding a property to the object that the array actually belongs to. So you will still be able to call that property like this:
array.hi // -> "weird"
…but this item is not actually an element in your array, so the below array, even though it's full of properties still only has a length of one.
const array = [];
array.push(1);
array['a'] = 'funky';
array.length; // -> 1
Even more interesting, this only happens when trying to pass an index with an alpha character in it. Even if you pass a numerical string, it will still push the value to the array in the usual/expected way. It's when alpha characters are present that arrays fall back to assigning properties instead of pushing array items.
Stranger yet — you can not only reference these properties without them "counting against" your array length, but you can actually store entire arrays and objects in these properties too as you would any other JS object.
const array = [];
array.push(1);
array['a'] = { b: 'test1', c: ['test2', 'test3']};
array.a.c[1]; // -> "test3"
array.length; // -> 1
Yes and no. This behavior is actually there to be consistent with the object-oriented nature of JavaScript as a whole. At the same time, as you mentioned, this is not consistent with the usual usage of JavaScript arrays and in most cases would likely baffle another developer working on a project where this was used in practice, so I wouldn't advise assigning properties to an array as a means of entry into that array, as it does not function that way.
However, properties are useful in all objects, and it is perfectly safe to add custom properties to any object as long as you are careful not to overwrite existing ones you might need. When you initialize an array []
, it has a length of 0
, right? Right. But does it already have inherent custom properties? Yep!
If you run the below code, you get ["length"]
as a property name that already exists on an empty array.
Object.getOwnPropertyNames([]); // -> ["length"]
Similarly, if you add your own properties, they will also show up when calling this method, along with your array elements:
let array = ['a','b','c'];
array['test'] = true;
Object.getOwnPropertyNames(array); // -> ["0", "1", "2", "length", "test"]
More practically, you may have a time when you want to add a special function to an array in your project but not necessarily want that function to spread across all other arrays. Rather than implementing this as a new prototype function, you can add this new function to a specific array by adding the function as the value of a property as you did in your example. Your properties can even interact with each other.
let array = ['a','b','c'];
array.doubleLength = function() { return this.length * 2 };
array.hasAtLeast = function(x) { return this.length >= x };
array.length; // -> 3
array.doubleLength(); // -> 6
array.hasAtLeast(1); // -> true
array.hasAtLeast(3); // -> true
array.hasAtLeast(4); // -> false
object, hashtable, LinkedList
?I discussed this in more detail above as well. Essentially, what is happening here is that the new properties are being stored as object properties, but not in the usual object { key: value }
key-value pair sort of way. Arrays and objects are both actually different types of what we'll refer to here as "raw objects". Everything in JavaScript is some instance of a raw object in one way or another. Arrays are a type of "raw" object. Key-value pair objects are a type of object and are also not these "raw" objects we are talking about, but another manifestation of them.
All objects shared a few key attributes. One of those attributes is that they all share the same ability to store properties, which we normally recognize as the keys in the objects we work with on a daily basis. All objects allow the addition of custom properties.
Some types of objects like strings require you to traverse to their prototypes in order to add properties. Because of this, this won't work:
let string = "Hello World";
string.addExclamation = function() { return this + "!" };
string.addExclamation() // -> THROWS ERROR!!!
…but this WILL work:
let string = "Hello World";
string.__proto__.addExclamation = function() { return this + "!" };
string.addExclamation(); // -> "Hello World!"
Other types of objects including both key-value objects and arrays, allow you to add custom properties directly to them without traversing to their prototypes, as I mentioned in my answer to your first question above.
This distinction between which types of objects allow the direct adding of properties and which don't, and which we'd consider "raw" objects and which we wouldn't boils down to a JavaScript ideology known as "primitive types" and "reference types". I highly suggest reading up on that "primitive vs. reference" distinction more.
It's also useful to note how to set up these prototype properties and the difference between prototype
and __proto__
. In many ways, prototype
and __proto__
work the same, the key difference being that you can call the __proto__
property on any variable/object to get the prototype for its object type. On the other hand, prototype
can be called only on the actual constructor for a variable/object. The below two prototype
and __proto__
lines actually call the same object.
const string = "";
string.__proto__; // -> String { … }
String.prototype; // -> String { … }
string.__proto__ === String.prototype; // -> true
Nope! This is about as raw as it comes when working with JavaScript. Just the beauty of object-oriented programming. I explain this in a lot more depth in the above two answers to your questions, as well as the content I added before my answers.
Contrary to what a lot of answers are saying, yes 👏🏼 do use arrays like this IF your situation calls for it. I don't mean to contradict myself here. Earlier, I did say you may want to avoid this, and that's true, so allow me to elaborate.
Array.prototype
which would affect all arrays, this is a perfectly fine and safe way to do so. I'll add my same example from my answer to question #1 below for reference:let array = ['a','b','c'];
array.doubleLength = function() { return this.length * 2 };
array.hasAtLeast = function(x) { return this.length >= x };
array.length; // -> 3
array.doubleLength(); // -> 6
array.hasAtLeast(1); // -> true
array.hasAtLeast(3); // -> true
array.hasAtLeast(4); // -> false
For further reading, I would highly encourage reading my post in its entirety and chewing on it, and then checking out the MDN docs on the prototype chain. For even further study, I highly suggest signing up for Just JavaScript, a completely free 10-part email digest series and takes you on a deep dive into JavaScript Mental Models, which focuses largely on this prototype chain and other fascinating parts of what makes JavaScript so robust. It's written by Dan Abramov, one of the co-creators of Redux, and beautifully illustrated by Maggie Appleton. Definitely worth a peek!