Is a JavaScript Array Index a String or an Integer

Is a JavaScript array index a string or an integer?

That is correct so:

> var a = ['a','b','c']
undefined
> a
[ 'a', 'b', 'c' ]
> a[0]
'a'
> a['0']
'a'
> a['4'] = 'e'
'e'
> a[3] = 'd'
'd'
> a
[ 'a', 'b', 'c', 'd', 'e' ]

What is the difference between using a string or a number to index an array in Javascript?

The indexes are stored internally as strings.

But it's more common practise to use numbers to access an array by it's index.

A property name P (in the form of a String value) is an array index if
and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not
equal to 2^³²−1

It's explained in this old post

Below is a snippet that showcases it.

Accessing the index with a string works fine as long that index string only contains digits.

But by expressing the index as a word then it can only be accessed with that word. And console logging the array doesn't show it.

let arr = [0,'a one','a two','a three'];arr['four'] = 'a four';arr.push('5');arr.push(6);arr.push([7,'7']);arr.push({a:8, b:'8'});

console.log('\nLog the array:\n\n');console.log(arr);
console.log('\nAccessing the array:\n\n');console.log("arr[0]:\t" + arr[0]);console.log("arr[\'1\']:\t" + arr['1']);console.log("arr[\'two\']:\t" + arr['two']);let i=2;console.log("arr[++i]:\t" + arr[++i]);console.log("arr[\'four\']:\t" + arr['four']);console.log('arr[4]:\t'+ arr[4]);console.log('arr[5]:\t'+ arr[5]);
console.log('\nListing the types in the array:\n\n');for (var a in arr) console.log(a+'\tindex type: '+ typeof a +', value: '+ arr[a] + ', value type: '+ typeof arr[a]);

Why does an array allow a string as an index in JavaScript?

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…

  1. It's not an associative array, because JS does not actually support them unless we are synonymizing "associative array" and "object".
  2. It IS an array, which is why Array.isArray(array) returned true.
  3. EPIPHANY moment — arrays ✨ ARE ✨ objects. Hear me out /li>

TL;DR

Here are the TL;DR versions of my answers to your questions. For more context and examples, keep scrolling…

  1. On the surface, it does seem inconsistent, but it's actually very consistent when you take a deeper look at the prototype chain and primitive types vs. reference types.
  2. Your array is an array even behind the scenes, but arrays in JS are a type of object and thus can be assigned custom properties in addition to their usual array elements.
  3. Nope! No specific engine needed for this. This is just pure JavaScript.
  4. If you require keys to be numeric and alphanumeric, use an object. HOWEVER, if you would like to use an array, it's perfectly okay to add custom properties to that array as well. Just keep in mind they are only properties of the object, not array elements.

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.

The long version

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

Answers to your questions

1. Why have this behavior? This seems inconsistent, right?

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

2. What is the data structure actually storing behind the scene: as an array or something like 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

3. Whether this behavior depends on a specific Javascript Engine (V8, SpiderMonkey, etc..) or not?

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.

4. Should I use an array like this (keys are both numeric index and string) over a normal object?

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.

  • Should you add elements to arrays like this, where it adds them as properties and not as array elements? No. Adding to arrays like this is actually adding JS properties to them, not array elements.
  • Is there ever a case where I SHOULD add properties to arrays like this? Yes! It may not be often, and I explain this in more detail in my response to your first question, but if you are in a situation where you want to add custom properties, even functional properties to one specific array and not the 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!

Why does the typeof a numerical array index in a for..in loop considered a string?

The for(x in y) syntax is intended to iterate over the properties of an object (not the indexes of an array), and property names are always stored as strings.

The fact that it also works for arrays is a side effect of array elements being properties on the array object.

To understand the difference, consider this code:

var s_array = new Array();
s_array[0] = 'foo';
s_array[1] = 'bar';
s_array['foo'] = 'bar';

console.log("Object:");
for(i in s_array) {
console.log(i);
}
console.log("Array:");
for(var i = 0, l = s_array.length; i < l; i++) {
console.log(i);
}

which provides the following output:

Object:
0
1
foo
Array:
0
1

There's a foo property on the object, but it's not actually an element inside the array.

How does JavaScript determine if an array index is integer?

JavaScript arrays aren't really arrays, they're JavaScript objects that have prototype methods that cause them to act like arrays. arr['one'] = 1 is valid JavaScript.

The way arr.length works is simply by looking at the array's keys, finding the largest number (JavaScript doesn't really do integers, just floats) and returning that number + 1.

try:

var arr = [];

arr.one = 1;
arr[8] = 1;
console.log(arr.length);

Why is the index of an array a string in the below code?

From MDN for...in

Note: for...in should not be used to iterate over an Array where the index order is important.

... The for...in loop statement will return all enumerable properties,
including those with non–integer names and those that are inherited.

When using for...in, the key is always a string and all it does is string concatenation.

You have an array, so better use Array.foreach() like so:

var arrayOfNumbers = [1, 2, 3, 4, 5, 6, 78];
arrayOfNumbers.forEach(function(item, index){
console.log(index + 1); // Here the index is a number!
});

Why for var in array returns a string index?

My friend, behold the wonder and curse of prototypical inheritance. What you are iterating over with for..in isn't an array. You are iterating over an Array object. Take a look at the doc:

Array indexes are just enumerable properties with integer names and
are otherwise identical to general Object properties. There is no
guarantee that for...in will return the indexes in any particular
order and it will return all enumerable properties, including those
with non–integer names and those that are inherited.

Because the order of iteration is implementation dependent, iterating
over an array may not visit elements in a consistent order. Therefore
it is better to use a for loop with a numeric index (or Array.forEach
or the for...of loop) when iterating over arrays where the order of
access is important.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

So what you are dealing with are enumerable "integer names" of properties in an Array object, not numerical order.

EDIT for v in array in JavaScript isn't equivalent to for v in list in python. It's equivalent to for k, v in dict.iteritems() but k is implicitly given to you as a string with integer name so to speak, i.e. "1", "2", "3", etc. I think it's counter-intuitive as well from an OO standpoint. How can a list/array be a dict-like object? But from a prototypical standpoint, so long as anything inherits from Object.prototype, it can be considered an Object with keys and properties.

for( in ) loop index is string instead of integer

You have got several options

  1. Make conversion to a Number:

    parseInt(i) === 1
    ~~i === 1
    +i === 1
  2. Don't compare a type (Use == instead of ===):

    i == 1 // Just don't forget to add a comment there
  3. Change the for loop to (I would do this but it depends on what you are trying to achieve):

    for (var i = 0; i < arr.length; i++) {
    if (i === 1) { }
    }

    // or

    arr.forEach(function(item, i) {
    if (i === 1) { }
    }

By the way you should not use for...in to iterate through an array. See docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

for..in should not be used to iterate over an Array where index order
is important. Array indexes are just enumerable properties with
integer names and are otherwise identical to general Object
properties. There is no guarantee that for...in will return the
indexes in any particular order and it will return all enumerable
properties, including those with non–integer names and those that are
inherited.

Because the order of iteration is implementation dependent, iterating
over an array may not visit elements in a consistent order. Therefore
it is better to use a for loop with a numeric index (or Array.forEach
or the non-standard for...of loop) when iterating over arrays where
the order of access is important.

Why does a string index in an array not increase the 'length'?

Javascript arrays cannot have "string indexes". A Javascript Array is exclusively numerically indexed. When you set a "string index", you're setting a property of the object. These are equivalent:

array.a = 'foo';
array['a'] = 'foo';

Those properties are not part of the "data storage" of the array.

If you want "associative arrays", you need to use an object:

var obj = {};
obj['a'] = 'foo';

Maybe the simplest visualization is using the literal notation instead of new Array:

// numerically indexed Array
var array = ['foo', 'bar', 'baz'];

// associative Object
var dict = { foo : 42, bar : 'baz' };

How to make associative array with number as string in Javascript

You have to declare associative arrays using {}, which creates a new object, because in JavaScript, arrays always use numbered indexes.

You need to declare an object: var arr={};

  • arrays use numbered indexes.
  • objects use named indexes.

var index = 100;var arr ={};arr[index.toString()] = "Hello";console.log(arr);


Related Topics



Leave a reply



Submit