Trivia time! What does the following array look like?
Object.keys({
2: true,
1: true,
'00': true,
'b': true,
'a': true,
'3': true,
})
Answer (click to see)
[ "1", "2", "3", "00", "b", "a" ]
So what exactly is going on?? Here are the ordering rules:
- Numbers are ordered first, and they are ordered within themselves from smallest to largest as long as they are
>=0
(see below for more details) - Strings come second, and they are ordered within themselves by insertion order
- Symbols come last, and they are ordered within themselves by insertion order (note that we didn't use symbols in this example)
But wait, why did '3'
come before '00'
if strings are ordered within themselves by insertion order?
Well, turns out that JS will see if your string can be converted to a number - if it can, then it will order it with the numbers and not the strings.
And what about '00'
? Apparently it converts it to a new number, then does something similar to toString()
on new number, and compares that new string with the original string.
If they match, then it can be lumped in with the numbers. If it doesn't match, then it's a string.
const originalString = '00'
const stringToNumber = Number(originalString)
const matchesOriginalString = stringToNumber.toString() === originalString // false: '0' !== '00'
Pretty clear, huh? :P
Thanks to this article for helping https://www.stefanjudis.com/today-i-learned/property-order-is-predictable-in-javascript-objects-since-es2015/
Here's the wording from the spec:
Numbers
For each own property key P of O such that P is an array index, in ascending numeric index order, Add P as the last element of keys.
i.e. insert numerical keys first in ascending order
Strings
For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, Add P as the last element of keys.
i.e. insert string keys in order of creation as long as they're not an array index
. So what's that?
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 ≤ 253 - 1. An
array index
is an integer index whose numeric value i is in the range +0 ≤ i < 232 - 1.
i.e. a string key that is a canonical numeric string and greater than +0
so... what's a canonical numeric string?
[A canonical numeric string is an] argument converted to a Number value if it is a String representation of a Number that would be produced by ToString, or the string "-0".
source for canonical numeric string
i.e. if that string is the same as any number that is toString()
Symbols
For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, Add P as the last element of keys.
i.e. insert symbol keys in order of creation
Top comments (4)
I was looking to link to a description of Object iteration order, and this is one of the first search results. But it is not really correct, as can be seen with this variation:
Note that
'1'
still comes before'3'
, even though'1'
was specified as a String and3
as aNumber
. Also'b'
comes before'00'
because of insertion order. It has nothing to do with the numeric value of'00'
.The main point is that Objects do not have numeric keys. If you try to specify one, it is converted to a string before it's stored. All Object keys are either Strings or Symbols.
When iterating an object, the system looks first for Strings that look like integer keys, and iterates those in numeric order, then iterates the remaining String keys, in insertion order, then iterates the Symbols, again in insertion order.
It's still a hot mess, but it's a different hot mess than you suggest.
I believe this is actually covered in my article above, with an emphasis on this line:
Ah... JavaScript the Bad Bits!
Thank you for the explanation. :)
The title of the article should have been
"The evil ways of ordering properties in an object." :D