Question

I have been playing with javascript arrays and I have run into, what I feel, are some inconsistencies, I hope someone can explain them for me.

Lets start with this:


var myArray = [1, 2, 3, 4, 5];
document.write("Length: " + myArray.length + "<br />");
for( var i in myArray){
   document.write( "myArray[" + i + "] = " + myArray[i] + "<br />");
}
document.write(myArray.join(", ") + "<br /><br />");
Length: 5
myArray[0] = 1
myArray[1] = 2
myArray[2] = 3
myArray[3] = 4
myArray[4] = 5
1, 2, 3, 4, 5

There is nothing special about this code, but I understand that a javascript array is an object, so properities may be add to the array, the way these properities are added to an array seems inconsistent to me.

Before continuing, let me note how string values are to be converted to number values in javascript.

  • Nonempty string -> Numeric value of string or NaN

  • Empty string -> 0

So since a javascript array is an object the following is legal:


myArray["someThing"] = "someThing";
myArray[""] = "Empty String";
myArray["4"] = "four";

for( var i in myArray){ document.write( "myArray[" + i + "] = " + myArray[i] + "<br />"); } document.write(myArray.join(", ") + "<br /><br />");

Length: 5
myArray[0] = 1
myArray[1] = 2
myArray[2] = 3
myArray[3] = 4
myArray[4] = four
myArray[someThing] = someThing
myArray[] = Empty String
1, 2, 3, 4, four

The output is unexpected.

The non empty string "4" is converted into its numeric value when setting the property myArray["4"], this seems right. However the empty string "" is not converted into its numeric value, 0, it is treated as an empty string. Also the non empty string "something" is not converted to its numeric value, NaN, it is treated as a string. So which is it? is the statement inside myArray[] in numeric or string context?

Also, why are the two, non numeric, properities of myArray not included in myArray.length and myArray.join(", ")?

Was it helpful?

Solution

The keys of a JavaScript array are actually strings. For details and an implementation of a map type for arbitrary keys, check this answer.


To clarify and add to what Jason posted: JavaScript arrays are objects. Objects have properties. A property name is a string value. Therefore, array indices are converted to strings as well before anything more can happen. A property name P will be treated as an array index (ie the special array-magic will be invoked) if the following holds (ECMA-262, 15.4):

ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 2^32 − 1

That numeric indices will be converted to strings (and not the other way around) can be easily verified:

var array = [];
array[1] = 'foo';
array['1'] = 'bar';
array['+1'] = 'baz';
document.writeln(array[1]); // outputs bar

Also, its bad practice to iterate over an array's entries with a for..in loop - you might get unexpected results if someone messed with some prototypes (and it's not really fast, either). Use the standard for(var i= 0; i < array.length; ++i) instead.

OTHER TIPS

(edit: the following is not quite right)

The keys of a JavaScript Object are actually strings. A Javascript Array by itself has numeric indices. If you store something with an index that can be interpreted as a nonnegative integer, it will try to do so. If you store something with an index that is not a nonnegative integer (e.g. it's alphanumeric, negative, or a floating-point number with a fractional piece), it will fail on the array-index store, and default to the Object (which is Array's base class) store, which then converts the argument to a string and stores by string index -- but these stored properties are not seen by the Array class and therefore are not visible to its methods/properties (length, join, slice, splice, push, pop, etc).

edit: the above is not quite right (as Christopher's foo/bar/baz example shows). The actual storage indices according to the ECMAscript spec are, in fact, strings, but if they are valid array indices (nonnegative integers) then the Array object's [[Put]] method, which is special, makes those particular values visible to the Array's "array-ish" methods.

This is an answer to PhiLo's post. His benchmark is flawed because he uses different property names for the object version: He should have used i as well and not x.

If done correctly, eg like this:

var start, end, count = 1000000;

var obj = {},
    array = [];

start = new Date;
for(var i = count; i--; )
    array[i] = i;
end = new Date;
document.writeln(Number(end) - Number(start));

start = new Date;
for(var i = count; i--; )
    obj[i] = i;
end = new Date;
document.writeln(Number(end) - Number(start));

You'll see that the times will be very close. In FF3.0.5, the array version is even consistently slower (in Opera, it's the other way around).

Arrays, like everything else in JavaScript, are objects. Objects have been blessed with the dot notation to ease the burden on developers. Using your example

myArray["someThing"] = "someThing";

is the same as writing

myArray.someThing = "someThing";

In this case, you are adding a property onto the object instead of adding it into the array. The same goes with the empty string, although you cannot use the dot notation for an empty string...strange, huh?

In the case of "4", it can be coerced into an integer, and therefore is used as an index into the array.

I disagree with Christoph when he states "array indices are converted to strings".

First, I think it is implementation dependent... I suppose (good) implementers will optimize array access, there are some smart ways to do it.

Actually, I did a little test, and although it is as good as most micro-benchmarks (ie. not super-reliable), it is interesting:

result = ""
var x;

var trueArray = []
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "i" + i; // To do the same operations
  trueArray[i] = 1;
}
var endTime = new Date();
result += "With array: " + (endTime - startTime) + "\n";

var sArray = []
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "" + i;
  sArray[x] = 1;
}
var endTime = new Date();
result += "With s array: " + (endTime - startTime) + "\n";

var objArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "i" + i;
  objArray[x] = 1;
}
var endTime = new Date();
result += "With object(i): " + (endTime - startTime) + "\n";

var sobjArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "" + i;
  sobjArray[x] = 1;
}
var endTime = new Date();
result += "With s object: " + (endTime - startTime) + "\n";

var iobjArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "" + i;
  iobjArray[i] = 1;
}
var endTime = new Date();
result += "With i object: " + (endTime - startTime) + "\n";


// Then display result

On IE6, I get: With array: 1453 With object: 3547
On FF 3.0, I get: With array: 83 With object: 226
On Safari 3.1, I get: With array: 140 With object: 313
On Opera 9.26, for some reason I don't get the result, but if I reduce to the tenth of number of loops, I get: With array: 47 With object: 516
Actually, I let Opera run while I type this, and finally got the result: With array: 281 With object: 166063...

So arrays are optimized! Which is fortunate...
Christoph's demonstration didn't impress me. My conclusion would be more that strings that can be interpreted as numbers are treated as such, which go along with the quoted formula...

So my interpretation of your results is that the array is behaving like a fast array with numerical indices when feed with these (with perhaps a behavior of associative array on sparse values, ie. some isolated big indices), but as an object, it still has the normal handling of properties. But these properties aren't handled in the array part, hence the result you got with join().

[EDIT] I added some loops, following Christoph's idea.
On FF3, I get: With array: 92 With s array: 93 With object(i): 243 With s object: 194 With i object: 125 (perfs vary between runs, but are roughly consistent).

I am not super-convinced of this integer -> string -> integer round-trip, not even that ECMA requests this sequence. The way I read it is: is the property is a string and can be interpreted as integer, then it is treated as such.

Of course, the only sure way to know would be to look at an implementation...

I notice with interest that plain objects getting an integer property or a property that can be transformed to an integer are somehow optimized. Perhaps because lot of JS programmers used plain objects as arrays, so implementers judged interesting to optimize this case.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top