Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting properties in Javascript is broken

Tags:

javascript

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:

  • (test1) if the keys are below 2^32 (4,294,967,296), they are automatically reordered, the smallest first
  • (test2) if the keys are above 2^32, they are NOT reordered.

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.

Update

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.

like image 668
Kar.ma Avatar asked Dec 19 '19 10:12

Kar.ma


2 Answers

This is expected. Per the specification, the method that iterates over properties, OrdinaryOwnPropertyKeys, does:

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

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

like image 182
CertainPerformance Avatar answered Nov 16 '22 01:11

CertainPerformance


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 2^32-1. So anything above that limit is a string key actually.

like image 25
obscure Avatar answered Nov 16 '22 01:11

obscure