Fundamentals 10 min read

Why Does Object.keys Return Keys in a Surprising Order? A Deep Dive into ECMAScript 2022

This article explains why Object.keys() may return property keys in an order different from their declaration, by dissecting the ECMAScript 2022 specification and showing how numeric, string, and Symbol keys are classified and sorted during execution.

Node Underground
Node Underground
Node Underground
Why Does Object.keys Return Keys in a Surprising Order? A Deep Dive into ECMAScript 2022

An Interesting Question

Object.keys()

is a commonly used method to get the list of keys from an object, but its returned order may differ from the declaration order.

// Node v14.16.1
const object = { a: 'x', c: 'x', 55: 'x', 1: 'x', b: 'x' };
console.log(Object.keys(object)); // ['1','55','a','c','b']

It appears the engine processes numeric keys first, sorting them ascending, then the string keys in declaration order.

First, extract all numeric keys and sort them ascending.

Then, keep the remaining string keys in the order they were declared.

This article examines how the ECMAScript® 2022 Language Specification defines the order returned by Object.keys().

How to Start?

Many developers first look for the specification but may not know how to locate the relevant part. The latest ECMAScript spec is available at https://tc39.es/ecma262/ . Use the search box to find Object.keys().

Interpreting Object.keys(O)

The specification defines Object.keys(O) in three steps.

1. Let obj be ? ToObject(O).

Let obj be ? ToObject(O).

This creates a variable obj whose value is the result of ToObject(O), where O is the argument passed to Object.keys.

2. Let nameList be ? EnumerableOwnPropertyNames(obj, "key").

Let nameList be ? EnumerableOwnPropertyNames(obj, key).

This assigns to nameList the result of EnumerableOwnPropertyNames(obj, "key"). The first argument is the obj defined above; the second argument is the literal string key.

The specification for EnumerableOwnPropertyNames involves:

Assert that the argument O is an object.

Define ownKeys as O.[[OwnPropertyKeys]]().

Initialize properties as an empty list.

Iterate over ownKeys, and for each key that is enumerable and the key argument is "key", add it to properties.

Return properties, which is the list of enumerable property names of O.

3. Return CreateArrayFromList(nameList).

Return CreateArrayFromList(nameList).

This converts nameList into an array.

The sorting does not happen here. The actual ordering is performed by [[OwnPropertyKeys]], which classifies and sorts property keys as follows:

Numeric array indices (0 ≤ i < 2³²‑1) are collected and sorted ascending.

String keys are added in the order of their creation.

Symbol keys are added in creation order after strings.

Thus, in the initial example, numeric keys 1 and 55 appear first, followed by the string keys a, c, and b.

Array Index?

Consider this extended example with additional numeric properties:

// Node v14.16.1
const object = { a: 'x', c: 'x', 55: 'x', 1: 'x', b: 'x' };
object['-1'] = 'x';
object[Math.pow(2,32)-1] = 'x';
object[Math.pow(2,32)-2] = 'x';
console.log(Object.keys(object));
// ['1','55','4294967294','a','c','b','-1','4294967295']

Only Math.pow(2,32)-2 qualifies as an array index and is sorted before the other numeric keys, because the range for an array index is 0 ≤ i < 2³²‑1.

Further Thoughts

Object.keys

can also be called on strings or arrays, returning an array of index strings:

console.log(Object.keys(['a','b','c'])); // ['0','1','2']
console.log(Object.keys('abc')); // ['0','1','2']

Exploring how the specification defines these cases can deepen understanding of JavaScript’s core behavior.

Conclusion

The ECMAScript specification rigorously defines property ordering, and many seemingly mysterious JavaScript behaviors are rooted in these formal rules. Learning to read the spec helps developers grasp the language’s foundations and the rationale behind its design.

ECMAScriptSpecificationObject.keysproperty order
Node Underground
Written by

Node Underground

No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.