When it comes to Javascript "arrays" (referring to those with the Array
prototype, not typed arrays), they are essentially objects. This means that defining an array like:
var m = [];
m[268435461] = -1;
is equivalent to:
var m = {
"268435461": -1
}
The key difference lies in the fact that in the first case, m
has the Array
prototype along with a special length
property.
Although methods from Array.prototype
(such as forEach
or join
) attempt to mimic sequential arrays found in other languages, they work by iterating through the array, increasing the loop counter starting from 0
up to length-1
, and accessing values based on the key String(i)
.
It's important to note that the length
of an array does not indicate the number of elements present but instead represents the highest numerical value of its keys plus one. In the example given, length
would be 268435462
when checked.
When you perform m < 0
comparison, JavaScript converts both non-number and number into strings. This process involves invoking Array.join
which utilizes a looping mechanism to convert elements to strings and separate them with commas:
Illustration:
m = [];
m[50] = 1;
console.log(m.join())
The above operations require significant memory allocations, leading to delays in execution.
(Further testing reveals that memory allocations are not solely responsible for the slowdown. Even "hollow" loops can result in similar delays:
console.time('small-init')
var m = [];
m[1] = -1;
console.timeEnd('small-init')
console.time('small-loop')
m.forEach(x => null)
console.timeEnd('small-loop')
console.time('big-init')
var m = [];
m[1e8] = -1;
console.timeEnd('big-init')
console.time('big-loop')
m.forEach(x => null);
console.timeEnd('big-loop')
Despite these observations, modern JS engines are not entirely simplistic and do implement optimizations specific to arrays. However, these optimizations cater more towards "good" sequential arrays rather than unusual scenarios like the one showcased here. In conclusion: avoid such practices!