4

I was writing a simple code exercise solution and came across what appears to be object.entries() doing an internal numeric ascending sort. I was expecting to have to sort the key value pairs myself since the docs say that you must do a sort if you need a specific order. Why is this is happening and can I rely on this behavior?

/*
Given an input of [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20], 
make a function that organizes these into individual array that is ordered. 
The above input should return:
[[1,1,1,1],[2,2,2],4,5,10,[20,20],391,392,591].
*/
const cleanTheRoom = (mess) => { 
  let hash = {};
  for (let i = 0; i < mess.length; i++) {
    (hash.hasOwnProperty(mess[i])) ? hash[mess[i]]++ : hash[mess[i]] = 1;
  }
  const clean = [];
  for (const [key, value] of Object.entries(hash)) {
    (value > 1) ? clean.push(Array(value).fill(key)) : clean.push(key);
  }
  return clean;
}

let cleaned = cleanTheRoom(
  [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20]
);

console.log(cleaned);
apena
  • 2,091
  • 12
  • 19
  • 2
    "*Why is this is happening?*" - because "integer index" properties are enumerated in order even on objects. "*and can I rely on this behavior?*" - no, you should not, always treat objects as unordered collections. Also you should consider using a `Map` if you want to use numbers as keys, your object will cause `key` to become a string. – Bergi Aug 08 '20 at 17:17
  • @Bergi - In 2020, I don't think we can really justify telling people not to rely on this specific behavior (anymore). You'd need an exotic object with differing behavior that people were likely to encounter to really be able to justify it, and I just don't see such an exotic object being defined. Or do you have an example? :-) You've caught me out before... ;-) – T.J. Crowder Aug 08 '20 at 17:20
  • @T.J.Crowder Well, the OP's code will break as soon as one of the numbers in his `cleanTheRoom` input is negative or has decimal digits. And yeah, it might be that one *can* rely on this behaviour, but the real question is whether one *should* - and objects not being meant for ordered collections hasn't changed. – Bergi Aug 08 '20 at 17:25
  • If you want to enumerate in the order of insertion, you can use a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) instead. Please read: [The traversal order of object properties in ES6](https://2ality.com/2015/10/property-traversal-order-es6.html) and [Does JavaScript Guarantee Object Property Order?](https://stackoverflow.com/questions/5525795). – adiga Aug 08 '20 at 17:30
  • 1
    @adiga - Worth noting that there have been updates since ES2015; now, even `Object.keys` and such have to follow the order for own properties. Bergi - I meant array indexes, which won't be negative of course. :-) (And I still don't think I'd rely on it in my own code, mostly because I just don't see a reason to.) – T.J. Crowder Aug 08 '20 at 17:35
  • 1
    I agree, just because you could doesn't mean you should. Nice to know if you can though, even if you absolutely wouldn't ;) – apena Aug 08 '20 at 17:44
  • @T.J.Crowder what new changes have been made? My understanding was the keys are enumerated in the order of integer-like keys, other keys in insertion order, symbols. – adiga Aug 08 '20 at 18:26
  • 1
    @adiga - `for-in` and `Object.keys` were explicitly exempted from following order in ES2015. ES2020 made `Object.keys` (and `values` and `entries`) follow the order, and further specified what `for-in` should do. – T.J. Crowder Aug 09 '20 at 12:37
  • 1
    @T.J.Crowder In the [2ality post](https://2ality.com/2015/10/property-traversal-order-es6.html#operations-that-traverse-properties), it lists `for..in` and `Object.keys` as well. But, someone in the comments has [already corrected them](https://2ality.com/2015/10/property-traversal-order-es6.html#comment-2325593746). Did the browsers implement that order anyway in `Object.keys()` and `for..in` for consistency? Because IIRC, I've always seen integer-like keys moving to the the beginning in `Object.keys()` array. – adiga Aug 09 '20 at 13:46
  • 1
    @adiga - I can't speak definitively, but the only major inconsistency I personally saw in implementations was whether the integer-index-named properties came before or after other properties. So if you had `{a: 1, 0: 2, b: 3, 1: 4}`, at one time one of the engines (SpiderMonkey?) gave you `"a", "b", "0", "1"` and another (V8?) gave you `"0", "1", "a", "b"`. The engine with minority behavior changed (after all, this was unspecified behavior, so people shouldn't have been relying on it), and the spec was updated to represent the then-current consensus. :-) – T.J. Crowder Aug 09 '20 at 21:18

2 Answers2

6

Object.entries uses the spec operation EnumerableOwnPropertyKeys, which in turn uses the object's [[OwnPropertyKeys]], which for most objects (including arrays) is OrdinaryOwnPropertyKeys. That operation specifically special-cases property names that look like array indexes and lists them in numeric order. The algorithm is:

  1. Let keys be a new empty List.
  2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
    1. Add P as the last element of keys.
  3. 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, do
    1. Add P as the last element of keys.
  4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
    1. Add P as the last element of keys.
  5. Return keys.
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Great to see the actual spec that defines this behavior (#2), thank you. I am not super familiar with how changes make ther way into the ECMA spec but can this behavior change in the future? – apena Aug 08 '20 at 17:30
  • 1
    @apena - Usually, behavior can only be specified more narrowly, not *changed*. There are exceptions to that, but they're rare. FWIW, if it were me, I probably wouldn't rely on it, but see the discussion on the question -- if you're dealing with array indexes, it's pretty hard for me to justify saying "dont do it" these days, so I'll stick with "I don't do it." :-) – T.J. Crowder Aug 08 '20 at 17:36
  • Probably mine is a dumb question, but tc39.es spec is a mirror site for ecma-international.org spec ? – dariosicily Aug 09 '20 at 10:18
  • 1
    @dariosicily - Not dumb. :-) https://tc39.es/ecma262/ is the official version of the most up-to-date specification. From that page: *"This document at https://tc39.es/ecma262/ is the most accurate and up-to-date ECMAScript specification. It contains the content of the most recent yearly snapshot plus any finished proposals (those that have reached Stage 4 in the proposal process and thus are implemented in several implementations and will be in the next practical revision) since that snapshot was taken."* The ECMA page lists the latest snapshot (ES2020). Snapshots are taken in June. – T.J. Crowder Aug 09 '20 at 12:35
  • 1
    Now I got it, thanks for your time and your detailed answer. – dariosicily Aug 09 '20 at 12:53
0

That's because Objects are meant to work as hashes on JavaScript, and in any implementation, hashes shouldn't be used to store insert-ordered values.

The main usage behind Object.entries is when you want to retrieve the keys/values of a specific Object without a specific order on either keys or values.

João Haas
  • 1,883
  • 13
  • 13
  • 2
    This is incorrect. 1. Objects are not hashes (they may or may not *use* hash maps in their implementation, but with modern engines, often not). 2. Regardless of #1, they *do* remember the order in which properties were created. 3. The `Object.entries` operation is specified such that the array indexes (the ones the OP is using) are listed numerically regardless of insertion order. – T.J. Crowder Aug 08 '20 at 17:39
  • @T.J.Crowder, I wasn't talking about implementation details, I was speaking only about conceptual design. Objects are meant to act as hashes, that's why the spec of `Object.entries` say it shouldn't return the inserted order (otherwise some users would use them as arrays). I agree that maybe my answer didn't specify the conceptual part, but I think understanding the concept behind a structure is more important than actual specs. – João Haas Aug 08 '20 at 17:50
  • *"Objects are meant to act as hashes..."* No, they aren't. They're things that contain properties and have a prototype. Hash **is** an implementation detail. *"...that's why the spec of Object.entries say it shouldn't return the inserted order..."* No, it doesn't. Please read my answer for what it says. – T.J. Crowder Aug 09 '20 at 12:32