JavaScript objects have no order stored for properties (according to the spec). Firefox seems to preserve the order of definition of properties when using a for...in<
This question come up as the top search result. After not finding a ordered hash, i just wrote this small coffescript. Hopefully this will help folks landing on this page:
## OrderedHash
# f = new OrderedHash
# f.push('a', 1)
# f.keys()
#
class OrderedHash
constructor: ->
@m_keys = []
@m_vals = {}
push: (k,v) ->
if not @m_vals[k]
@m_keys.push k
@m_vals[k] = v
length: () -> return @m_keys.length
keys: () -> return @m_keys
val: (k) -> return @m_vals[k]
vals: () -> return @m_vals
No, since the Object type is specified to be an unordered collection of properties, you can not rely on that. (Or: You can only rely on that an object is an unordered collection of properties.)
If you want to have an ordered hash set, you will need to implement it on your own.
Realize its late but I needed this and couldn't find it elsewhere. *UPDATE Added necessary non-enumerable methods and properties. Quick ES 5 implementation (polyfill as needed):
function orderedHash(object) {
'use strict'
var obj = object || {}
Object.defineProperties(this, {
'length': {
value: 0,
writable: true
},
'keys' : {
value: [],
writable: true
},
'sortedBy': {
value: '',
writable: true
}
})
this.hash(obj)
obj = null
}
Object.defineProperties(orderedHash.prototype, {
'sortByKeys': {
value: function sortByKeys() {
var i, len, name
this.keys.sort(function(a, b) {
return a >= b ? 1 : -1
})
for (i=0, len = this.keys.length; i < len; ++i) {
name = this.keys[i]
this[i] = this[name]
}
this.sortedBy = 'keys'
return null
}
},
'sortByValues': {
value: function sortByValues() {
var i, len, newIndex, name, ordered = [], names = this.keys.splice(0)
this.keys = []
for (i=0, len = this.length; i < len; ++i) {
ordered.push(this[i])
ordered.sort(function(a, b) {
return a >= b ? 1 : -1
})
newIndex = ordered.lastIndexOf(this[i])
name = names[i]
this.keys.splice(newIndex, 0 , name)
}
for (i=0, len = ordered.length; i < len; ++i) {
this[i] = ordered[i]
}
this.sortedBy = 'values'
return null
}
},
'insert': {
value: function insert(name, val) {
this[this.length] = val
this.length += 1
this.keys.push(name)
Object.defineProperty(this, name, {
value: val,
writable: true,
configurable: true
})
if (this.sortedBy == 'keys') {
this.sortByKeys()
} else {
this.sortByValues()
}
return null
}
},
'remove': {
value: function remove(name) {
var keys, index, i, len
delete this[name]
index = this.keys[name]
this.keys.splice(index, 1)
keys = Object.keys(this)
keys.sort(function(a, b) {
return a >= b ? 1 : -1
})
for (i=0, len = this.length; i < len; ++i) {
if (i >= index) {
this[i] = this[i + 1]
}
}
delete this[this.length - 1]
this.length -= 1
return null
}
},
'toString': {
value: function toString() {
var i, len, string = ""
for (i=0, len = this.length; i < len; ++i) {
string += this.keys[i]
string += ':'
string += this[i].toString()
if (!(i == len - 1)) {
string += ', '
}
}
return string
}
},
'toArray': {
value: function toArray() {
var i, len, arr = []
for (i=0, len = this.length; i < len; ++i) {
arr.push(this[i])
}
return arr
}
},
'getKeys': {
value: function getKeys() {
return this.keys.splice(0)
}
},
'hash': {
value: function hash(obj) {
var i, len, keys, name, val
keys = Object.keys(obj)
for (i=0, len = keys.length; i < len; ++i) {
name = keys[i]
val = obj[name]
this[this.length] = val
this.length += 1
this.keys.push(name)
Object.defineProperty(this, name, {
value: val,
writable: true,
configurable: true
})
}
if (this.sortedBy == 'keys') {
this.sortByKeys()
} else {
this.sortByValues()
}
return null
}
}
})
What happens here is that by using Object.defineProperty()
instead of assignment can we make the properties non-enumerable, so when we iterate over the hash using for...in
or Object.keys()
we only get the ordered values but if we check hash.propertyname
it will be there.
There are methods provided for insertion, removal, assimilating other objects (hash()
), sorting by key, sorting by value, converting to array or string, getting the original index names, etc. I added them to the prototype but they are also non-enumerable, for...in
loops still work.
I didn't take time to test it on non-primitives, but it works fine for strings, numbers, etc.