+7  A: 

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.

Christoph
This isn't quite true.
Jason S
@Jason: I just checked - I believe ECMA-262 15.4 agrees with me...
Christoph
We're both right. They're strings but the numerical indices get interpreted in a special way.
Jason S
@Jason: Nitpick: The standard says a property name P is treated specially if ToString(ToUint32(P)) is equal to P. The property name itself is a string value, so I stand by my statement
Christoph
Nitpick: my "We're both right" was meant to apply to your answer and mine. You are correct that my statement of "This isn't quite true" isn't quite true. :)
Jason S
@Jason: Ok. I have also added a small explanation (should be correct, or did I misrepresent anything?); and if everybody is right, we can all live happily ever after ;)
Christoph
looks good, you win ;) +1 for the foo/bar/baz example.(sympathy points always welcome...)
Jason S
@Jason: done - your answer and comments led to my extended answer, so you might as well get some of the credit...
Christoph
Just to be clear: I wrote I disagree, not that you are wrong and me right; I find the discussion interesting! :-)
PhiLho
@PhiLho: Yeah, the standard isn'T as clear as it could be; but I think my example is sufficient to prove whats going on...
Christoph
The standard is "normative", meaning it's written with Javascript implementers in mind, to unambiguously specify the language -- and only secondarily is it there to inform us poor old users. :( It would be nice if there were a companion guide for users which explained these subtleties in detail.
Jason S
+2  A: 

(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.

Jason S
A: 

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.

PhiLho
Read my answer again: Numeric indices *are* treated specially; but before that, they'll be treated as regular property names, ie strings! so if you do something like array[1], it will go number->string (because its a property name) and then string->number again (because of array magic)
Christoph
And it's not implementation dependant: check ECMA-262! Only the implementation of arrays is unspecified, not their semantic properties!
Christoph
Also, check my new answer - your benchmark is flawed!
Christoph
"I am not super-convinced of this integer -> string -> integer round-trip" - naturally, implementations can optimize this - but the standard requires that they behave as if they wouldn't
Christoph
The appropriate sections of ECMA-262: 15.4 (p. 100) and 15.4.5.1 (p. 109)
Christoph
pay special attention to 15.4.5.1, step 7 and following: the array magic happens AFTER the index was used as a regular property name - which means string!
Christoph
@Christoph: call me thick, but I read in 15.4.5.1: "Assume A is an Array object and P is a string." There is ambiguity as they don't address the case where P is an integer, but perhaps it is too obvious. Now, standard interpretation is delicate, see the W3C standards and how browsers interpret them!
PhiLho
@PhiLho: They don't address the case 'P is an integer' because it can't occur: The arguments to [[Put]] (the thing which will be called when you do `foo[???] = ???`) is a property name and a value; a property name is a string; there's nothing more that I can do than say it again and again ;)
Christoph
Christoph is correct. If you have questions about the spec, the mozilla.dev.tech.js-engine newsgroup is a good place to ask.
Jason S
A: 

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.

Michael Deardeuff
+1  A: 

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).

Christoph
Thanks for the remark and idea of additional tests. I wrote my answer as an edit of my message.Now, the important conclusion is your remark about looping on arrays, and the fact they are optimized for integer indices. The remainder is detail, no? :-)
PhiLho