I'm writing a JavaScript interpreter for extremely resource-constrained embedded devices (), and every time I think I have implemented some bit of JavaScript correctly I realise I am wrong.
My question now is about []
. How would you implement one of the most basic bits of JavaScript correctly?
I've looked through the JavaScript spec and maybe I haven't found the right bit, but I can't find a useful answer.
I had previously assumed that you effectively had two 'maps' - one for integers, and one for strings. And the array length was the value of the highest integer plus one. However this seems wrong, according to jsconsole on chrome:
var a = [];
a[5] = 42;
a["5"]; // 42
a.length; // 6
but also:
var a = [];
a["5"] = 42;
a[5]; // 42
a.length; // 6
So... great - everything is converted into a string, and the highest valued string that represents an integer is used (plus one) to get the length? Wrong.
var a = [];
a["05"] = 42;
a.length; // 0
"05"
is a valid integer - even in Octal. So why does it not affect the length?
Do you have to convert the string to an integer, and then check that when converted back to a string, it matches?
Does anyone have a reference to the exact algorithm used to store and get items in an array or object? It seems like it should be very simple, but it looks like it actually isn't!
I'm writing a JavaScript interpreter for extremely resource-constrained embedded devices (http://www.espruino.), and every time I think I have implemented some bit of JavaScript correctly I realise I am wrong.
My question now is about []
. How would you implement one of the most basic bits of JavaScript correctly?
I've looked through the JavaScript spec and maybe I haven't found the right bit, but I can't find a useful answer.
I had previously assumed that you effectively had two 'maps' - one for integers, and one for strings. And the array length was the value of the highest integer plus one. However this seems wrong, according to jsconsole on chrome:
var a = [];
a[5] = 42;
a["5"]; // 42
a.length; // 6
but also:
var a = [];
a["5"] = 42;
a[5]; // 42
a.length; // 6
So... great - everything is converted into a string, and the highest valued string that represents an integer is used (plus one) to get the length? Wrong.
var a = [];
a["05"] = 42;
a.length; // 0
"05"
is a valid integer - even in Octal. So why does it not affect the length?
Do you have to convert the string to an integer, and then check that when converted back to a string, it matches?
Does anyone have a reference to the exact algorithm used to store and get items in an array or object? It seems like it should be very simple, but it looks like it actually isn't!
Share Improve this question edited Oct 21, 2017 at 21:57 River 9,09215 gold badges56 silver badges68 bronze badges asked Apr 11, 2013 at 19:22 Gordon WilliamsGordon Williams 1,8862 gold badges17 silver badges31 bronze badges 6-
6
The standard says: "A property name
P
(in the form of aString
value) is an array index if and only ifToString(ToUint32(P))
is equal toP
andToUint32(P)
is not equal to2**32-1
.". – user1233508 Commented Apr 11, 2013 at 19:29 - 6 If you're writing an interpreter, assumptions shouldn't be used. You should be reading the spec. – user1106925 Commented Apr 11, 2013 at 19:45
- Why don't you use existing, powerful and standard-pliant JS implementations, like V8? What I read at espruino./Performance sounded horrible :-/ – Bergi Commented Apr 11, 2013 at 20:11
- @Bergi V8, like every other JIT piler, is rather hard to port and not a good fit for resource-constrained devices. Not only is the JIT piler's output architecture-specific, setting up the memory for that code is also OS-specific. And a JIT piler practically always uses more memory than a simple interpreter. They also need some time to warm up and bee fast. All these make them very ill-suited for obscure devices with few resources. – user395760 Commented Apr 12, 2013 at 13:37
- Thanks - I did in fact try and find this in the spec before posting, but I must have been looking in the wrong place. – Gordon Williams Commented Apr 12, 2013 at 20:47
4 Answers
Reset to default 3As the specs said, and was noted by others:
"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^32-1."
That's explain why in your scenario "5"
is considered an array index and "05"
is not:
console.log("5" === String("5" >>> 0));
// true, "5" is equal to "5", so it's an index
console.log("05" === String("05" >>> 0));
// false, "05" is not equal to "5", so it's not an index
Note: the Zero-fill right shift is the shortest way in JS to have a substitute of ToUint32
, shifting a number by zero.
See MDN
It's possible to quote the JavaScript array indexes as well (e.g., years["2"] instead of years[2]), although it's not necessary. The 2 in years[2] eventually gets coerced into a string by the JavaScript engine, anyway, through an implicit toString conversion. It is for this reason that "2" and "02" would refer to two different slots on the years object and the following example logs true:
console.log(years["2"] != years["02"]);
So with a["5"]
you are accessing the array while a["05"]
sets a property on the array object.
Arrays are just objects. That means they can have additional properties which are not considered elements of the array.
If the square bracket argument is an integer, it uses it to perform an assignment to the array. Otherwise, it treats it as a string and stores it as a property on the array object.
Edit based on delnan's ment and DCoder's ment, this is how JavaScript determines if it is an appropriate index for an array (versus just a property): http://www.ecma-international/ecma-262/5.1/#sec-15.4
Arrays are also objects.
By doing this
a["05"] = 5;
You are doing the same thing as:
a.05 = 5;
However, the above will result in a syntax error, as a property specified after a dot cannot start with a number.
So if you do this:
a = [];
a["05"] = 5;
you still have an empty array, but the property of a
named 05
has the value 5
.
The number x
is an array index if and only if ToString(ToUint32(x))
is equal to x
(so in case of "05"
that requirement is not met).