Lập trình · 21/10/2026

JavaScript Object.keys: Why Key Order Can Differ from Insertion Order

A status list appears in the wrong order even though entries were inserted correctly. Numeric-looking Object keys can make Object.keys return a different sequence. This matters when an API list becomes a lookup object and its keys later drive a menu.

JavaScript Object.keys: vì sao thứ tự khóa khác thứ tự thêm?

A status list appears in the wrong order even though entries were inserted correctly. Numeric-looking Object keys can make Object.keys return a different sequence. This matters when an API list becomes a lookup object and its keys later drive a menu.

1. Array-index keys come first

const labels = {};
labels['10'] = 'Review';
labels['2'] = 'Draft';
labels.featured = 'Featured';
labels['01'] = 'Pinned';
console.log(Object.keys(labels).join('|')); // 2|10|featured|01

For ordinary objects, array-index keys appear in ascending numeric order, followed by other string keys in property creation order. '01' is not an array index despite being numerically convertible. See ECMAScript OrdinaryOwnPropertyKeys.

Object.keys selects own enumerable string keys, excluding inherited properties, Symbols and non-enumerable properties. See MDN Object.keys. These examples concern ordinary objects rather than Proxies or every exotic object.

2. Updating and recreating are different operations

const menu = { home: 'Home', help: 'Help', contact: 'Contact' };
menu.help = 'Support';
console.log(Object.keys(menu).join('|')); // home|help|contact
delete menu.help;
menu.help = 'Support';
console.log(Object.keys(menu).join('|')); // home|contact|help

The final help value is identical, while enumeration changes in this example. An update implemented as deletion followed by insertion can therefore move a menu entry. A lookup table's incidental position should not define business priority.

3. Keep presentation order in an array

const rows = [
  { id: '10', label: 'Review' },
  { id: '2', label: 'Draft' }
];
const byId = Object.fromEntries(rows.map(row => [row.id, row]));
console.log(rows.map(row => row.label).join('|')); // Review|Draft
console.log(Object.keys(byId).join('|')); // 2|10

Here rows represents presentation order and byId supports lookup. Render rows rather than reconstructing the list from lookup keys. Define a duplicate-ID policy: an array can retain both records while a lookup stores one value for that key.

If the product requires priority or creation-date sorting, include that field and sort by an explicit rule. Insertion order alone does not express that requirement.

4. Use Map for insertion-ordered traversal

const menu = new Map([['10', 'Review'], ['2', 'Draft']]);
menu.set('10', 'Reviewed');
console.log([...menu.keys()].join('|')); // 10|2
menu.delete('10');
menu.set('10', 'Reviewed');
console.log([...menu.keys()].join('|')); // 2|10
console.log(Object.keys(Object.fromEntries(menu)).join('|')); // 2|10

Map iteration follows insertion order. Updating an existing entry retains its position; deleting and reinserting creates a new position. See MDN Map.

Converting back to Object does not preserve every ordering guarantee. Send an ordered array of records or key–value pairs when sequence is part of an API contract, rather than requiring consumers to interpret object member order like JavaScript.

5. Test the conversion boundary

  • Include '2', '10' and '01' in the same test.
  • Distinguish updates from deletion and recreation.
  • Make the source of presentation order explicit.
  • Handle duplicate IDs before building a lookup.
  • Check output after converting Maps or arrays to Objects.

All eight bilingual code blocks ran with Node.js, with extra array-index boundary and key-visibility checks. These are in-memory tests rather than browser UI or live API contract tests.

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.