My “evil twin,” Patrick Mueller, recently opined about a possible change in the Javascript (officially called ECMAScript) specification to make Javascript objects have a defined enumeration order for their properties. In Javascript, every object is basically a Hash which maps property names to property values.
The existing ECMAScript spec says that an object is an “unordered collection of properties.” But apparently recent drafts have omitted the word unordered.
It turns out that MOST existing JavaScript implementations enumerate object properties in the order in which those properties were added to the object. This was pointed out in an article by John Resig about the JavaScript engine in Google’s Chrome browser. The Chrome JavaScript VM had a few cases for which this was not the case. Despite the fact that the ECMAScript standard doesn’t/didn’t require a standard order, the Chrome team decided that the incompatibility with the other “major browsers” constituted a bug.
Patrick is not happy about this.
There’s an interesting parallel here with changes in Ruby Hashes between Ruby 1.8 and Ruby 1.9.
A few years back there were heated discussions on the Ruby-talk forum about the “need” for an ordered hash. One aspect of this is what the ordering should be. Some advocated that a Hash should be enumerated by the sorted order of the keys (assuming that the keys were all comparable) This would mean that a Hash with integer keys would enumerate similarly to an Array. Another camp wanted ordering by insertion order.
At the time my reaction to this was similar to Patrick’s Hashes are all about speed, and the whole notion of a Hash was based on what I’d learned about hashing in my computer science classes years ago. Hashes are inherently unordered, and adding ordering would introduce unnecessary overhead.
Then Matz snuck ordered hashes into Ruby 1.9.
Ruby 1.9 hashes enumerate in the order in which their elements were inserted. This is nice, but at what cost.
It turns out that this is mostly a space cost. In Ruby 1.8, each hash entry needs 4 things, the hash value, a reference to the key, a reference to the data, and a pointer to the next hash entry with the same hash value should there be a hash collision. Depending on the platform each of this is roughly a word.
Ruby 1.9 adds two more pointers to each entry, forward and backward pointers which allow the entries to be chained together in a doubly linked list. There are also two pointers to the first and last entries in the whole hash. So besides the space cost, there is a small additional cost for element insertion and deletion.
When an element is inserted (which is always at the end of the list), either the first entry pointer (if this is the first element) or the forward pointer in the last element needs to be updated, and the backward pointer in the new element gets set to the old last element, (or 0).
When an element is deleted, the forward pointer of the previous element (or the first pointer if we are deleting the last element) and the backward pointer of the next element (or the last pointer if we are deleting the last element) need to be updated.
The performance of looking up an element is unaffected, as long as the hash is just being read, the linked list will remain unchanged.
And what about enumerating.
It turns out that enumerating Ruby 1.9 hashes is slightly faster than enumerating Ruby 1.8 hashes, even though one might think that preserving enumeration order would be a burden.
In Ruby 1.8 enumerating a hash is done by visiting each entry in an array of entries, and checking whether or not it actually contains a value. Empty hash entries have a special value ‘reference’ indicating that the value is undefined.
In Ruby 1.9 enumerating a hash is done by starting with the first entry pointer and following the forward pointers. Every entry on the list is actually a defined element.
All in all the speed difference is rather minimal, and there are certainly cases where having a fixed enumeration order is nice.
In the case of JavaScript, since the major implementations all have fixed property enumeration orders, there are undoubtedly applications which rely on it, and Chrome’s decision, to go with the pragmatics of compatibility, rather than appealing to the lack of a specified ordering makes sense, at least to me.