Ordered hash in JavaScript

前端 未结 9 1019
心在旅途
心在旅途 2020-12-05 17:34

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<

9条回答
  •  难免孤独
    2020-12-05 17:59

    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.

提交回复
热议问题