I need to loop through a JavaScript object treating it as an array with custom keys. I know this is not fully supported, since properties have no instrinsic order, but since I always reorder the properties, I found this approach simple and reliable... until now.
The problem occurs when the keys are numbers, or strings that can be casted as numbers.
When I run this code:
var test1 = {4294966222:"A",4294966333:"A",4294966111:"A"};
var test2 = {4294968222:"A",4294968333:"A",4294968111:"A"};
for (var k in test1) {console.log(k);}
console.log("---");
for (var k in test2) {console.log(k);}
the output is:
4294966111
4294966222
4294966333
---
4294968222
4294968333
4294968111
Which means:
The question is: why is this happening?
Since all the browsers I tested (Google Chrome 79.0, Mozilla Firefox 71.0, Microsoft Edge 44.18362, Internet Explorer 11.535) agree about this output, there must be some official specification.
I tested a lot of numbers before finding out it was a threshold matter. I found odd that the sequence 2,3,1 behaves differently from three timestamps ordered in the same way.
This is expected. Per the specification, the method that iterates over properties, OrdinaryOwnPropertyKeys
, does:
For each own property key P of O that is an array index, in ascending numeric index order, do
a. Add P as the last element of keys.
For each own property key P of O that is a String but is not an array index, in ascending chronological order of property creation, do
a. Add P as the last element of keys.
The ascending numeric order only applies for properties which are array indicies.
So, what is an "array index"? Look it up::
An integer index is a String-valued property key that is a canonical numeric String (see 7.1.21) and whose numeric value is either +0 or a positive integer ≤ 2^53 - 1. An array index is an integer index whose numeric value i is in the range +0 ≤ i < 2^32 - 1.
So, numeric properties that are greater than 2^32 are not array indicies, and therefore iterated in order of property creation. However, numeric properties that are less than 2^32
are array indicies, and are iterated over in ascending numeric order.
So, for example:
1
: Array index, will be iterated over numerically
10
: Array index, will be iterated over numerically
4294968111
: Greater than 2 ** 32
, will be iterated over after array indicies are finished, in property creation order
9999999999999
: Greater than 2 ** 32
, will be iterated over after array indicies are finished, in property creation order
Also, keep in mind that, contrary to popular belief, property iteration order is guaranteed by the specification as well, thanks to the for-in iteration proposal which is stage 4.
This has to do with the way they keys of an object are traversed.
According to the ES6 specifications it should be:
9.1.12 [[OwnPropertyKeys]] ( )
When the [[OwnPropertyKeys]] internal method of O is called the following steps are taken:
Let keys be a new empty List.
For each own property key P of O that is an integer index, in ascending numeric index order
Add P as the last element of keys.
For each own property key P of O that is a String but is not an integer index, in property creation order
Add P as the last element of keys.
For each own property key P of O that is a Symbol, in property creation order
Add P as the last element of keys.
Return keys.
http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
That means if the value of a key stays the same if converted to an unsigned 53 bit number and back it is treated as an integer index which gets sorted in ascending numeric order.
If this fails it's treated as a string key, which are ordered the way they were added to the object.
The catch here is that all major browsers don't follow this specification yet and use an array index instead which is limited to a positive number up to . So anything above that limit is a string key actually.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With