Why Does a String Index in an Array Not Increase the 'Length'

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' };

Why doesn't the length of the array change when I add a new property?

Quoting ECMA Script 5 Specification of Array Objects,

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 232−1.

Since Hello is not valid, according to the above definition, it is not considered as an array index but just as an ordinary property.

Quoting MDN's Relationship between length and numerical properties section,

When setting a property on a JavaScript array when the property is a valid array index and that index is outside the current bounds of the array, the engine will update the array's length property accordingly

So, only if the property is a valid array index, the length property will be adjusted.

In your case, you have just created a new property Hello on the array object.


Note: Only the numerical properties will be used in all of the Array's prototype functions, like forEach, map, etc.

For example, the array shown in question, when used with forEach,

arr.forEach(function(currentItem, index) {
console.log(currentItem, index);
})

would print

Hello 0
There 1
123 2
456 3
{ show: [Function] } 4

even though the list of keys shows Hello.

console.log(Object.keys(arr));
// [ '0', '1', '2', '3', '4', 'Hello' ]

It is because, Array is derived from Object,

console.log(arr instanceof Object);
// true

and Hello is a valid key of the array object, but just not a valid array index. So, when you treat the array as an Object, Hello will be included in the keys, but the array specific functions will include only the numerical properties.

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!

Javascript Array operations on string-index array

That's because it's not a "string-indexed array." JavaScript doesn't have those. What you have there is an array (because you used []) but you're not using it as an array, you're using it as an object. (Arrays are also objects; see my A myth of arrays.) Those are object property names. If you're not using the array-ness of it, use {} instead of [] to create it.

There are a couple of operations that iterate property names:

  • You can get an array of an object's own, enumerable property names via Object.keys. There's also Object.getOwnPropertyNames.
  • You can loop through all enumerable properties (including inherited ones) with a for-in loop.
  • Plain objects aren't iterable, so we can't use for-of, but if you want something like a "string-indexed array" you might look at Map, which is iterable.

Here are a couple of examples:

let a = {};a['first element'] = 1;a['second element'] = 2;a['third element'] = 3;console.log("for-in:");for (let key in a) {  console.log(key);}console.log("Object.keys:");Object.keys(a).forEach(key => console.log(key));

Why does the method add() does not increase the size of the array?

The problem is in this part:

if (array.length == lastindex+1) {
int []tempa = new int[lastindex*2];

Here, when you add the third element, lastindex has the value of 1, array.length is 2, and in the if, the condition is true because 2 == 1+1. However, to calculate the new size of the new array then, you take the value of lastindex, which is still 1, so the new array again gets the size 2. What you want to do, is the following:

if (array.length == lastindex+1) {
int []tempa = new int[(lastindex+1)*2];

Trying to get Array length

As per spec

The value of the length property is numerically greater than the name
of every own property whose name is an array index; whenever an
own property of an Array object is created or changed, other
properties are adjusted as necessary to maintain this invariant.

Further

Specifically, whenever an own property is added whose name is an array
index, the value of the length property is changed.

So, length property is adjusted when the property whose name is an array index is created, only then length property changes.

'abc' being a non-numeric (and hence not an index of an array) when added to the array, doesn't reflect any change in length property.

Why does Array.map() method returns arr[index+1].length as undefined?

When you reach to the last index of strArr than with index+1 you're trying to access index which is not present in array, and this is cause of the this error strarr[(index + 1)] is undefined

function longestConsec(strarr, k) {    // your code    let solution=[];    strarr.map((string,index,arr)=>{    let arrayElementLength=strarr[index+1].length;    console.log(arrayElementLength);if(string.length==arrayElementLength){    solution.push(string);}    })    return solution;}
console.log(longestConsec(["zone", "abigail", "theta", "form", "libe", "zas"], 2))


Related Topics



Leave a reply



Submit