JavaScript中的有序哈希

时间:2010-05-09 19:37:25

标签: javascript

JavaScript对象没有为属性存储订单(根据规范)。在使用for...in循环时,Firefox似乎保留了属性定义的顺序。这种行为是我可以依赖的吗?如果没有,那么在某处实现有序散列类型的JavaScript代码?

10 个答案:

答案 0 :(得分:28)

2016年的JavaScript,特别是EcmaScript 6,支持Map built-in class

  

Map对象按插入顺序迭代其元素 - for循环返回每个迭代的[key,value]数组。

这就是你需要的。 (我想知道为什么这是这个数据结构描述中的第一个信息。)

例如,

m = new Map()

m.set(3,'three')
m.set(1,'one')
m.set(2,'two')

m // Map { 3 => 'three', 1 => 'one', 2 => 'two' }

[...m.keys()] // [ 3, 1, 2 ]

或来自docs的示例:

var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');

myMap // Map { 0 => 'zero', 1 => 'one' }

for (var [key, value] of myMap) {
  console.log(key + " = " + value);
}

for (var key of myMap.keys()) {
  console.log(key);
}

for (var value of myMap.values()) {
  console.log(value);
}

for (var [key, value] of myMap.entries()) {
  console.log(key + " = " + value);
}

答案 1 :(得分:24)

不,自Object type is specified to be an unordered collection of properties以来,你不能依赖它。 (或者:你只能依赖于一个对象是一个无序的属性集合。)

如果你想要一个有序的哈希集,你需要自己实现它。

答案 2 :(得分:19)

@Vardhan在纯JavaScript中的回答,使用闭包而不是经典的OO并添加了insert()方法:

function makeOrderedHash() {
    var keys = [];
    var vals = {};
    return {
        push: function(k,v) {
            if (!vals[k]) keys.push(k);
            vals[k] = v;
        },
        insert: function(pos,k,v) {
            if (!vals[k]) {
                keys.splice(pos,0,k);
                vals[k] = v;
            }
        },
        val: function(k) {return vals[k]},
        length: function(){return keys.length},
        keys: function(){return keys},
        values: function(){return vals}
    };
};

var myHash = makeOrderedHash();

答案 3 :(得分:7)

这个问题是最热门的搜索结果。在没有找到有序哈希之后,我只是写了这个小小的coffescript。希望这将有助于人们登陆此页面:

## 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

答案 4 :(得分:5)

我做的一个技巧是将数据存储在常规的无序散列中,然后将首选顺序存储在数组中。在JS中,您甚至可以使订单数组成为散列本身的一部分。

var myHash = {
  a: "2",
  b: "3",
  c: "1"
};

myHash.order = [ myHash.c, myHash.a, myHash.b ];

alert("I can access values by key. Here's B: " + myHash.b);
var message =  "I can access also loop over the values in order: ";

for (var i=0;i<myHash.order.length;i++)
{ 
  message = message + myHash.order[i] + ", ";
}

alert(message)

它不是很优雅,但它完成了工作。

答案 5 :(得分:1)

意识到它已经很晚了,但我需要这个并且无法在其他地方找到它。 *更新 添加了必要的非可枚举方法和属性。 快速ES 5实施(根据需要进行polyfill):

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
        }
    }
})

这里发生的是通过使用Object.defineProperty()而不是赋值,我们可以使属性不可枚举,所以当我们使用for...inObject.keys()迭代哈希时,我们只得到有序的值,但如果我们检查hash.propertyname它将在那里。 提供了插入,删除,同化其他对象(hash()),按键排序,按值排序,转换为数组或字符串,获取原始索引名称等方法。我将它们添加到原型中但是它们也是不可枚举的,for...in循环仍然有效。 我没有花时间在非原语上测试它,但它适用于字符串,数字等。

答案 6 :(得分:0)

一种相当简单的方法是使用数组来存储订单。 您需要编写自定义比较函数来建立所需的顺序。 它的缺点是你必须对数组进行排序并跟踪关系,每次更改哈希表。 如果你必须维护一个不断变化的哈希列表,它就不是很整洁。

var order=[];
var hash={"h1":4,"h2":2,"h3":3,"h4":1};

function cmp(a,b) {
  if (hash[a] < hash[b]) return -1;
  if (hash[a] > hash[b]) return 1;
  return 0;
}

// Add initial hash object to order array
for(i in hash) order.push(i);
order.sort(cmp);
// h4:1 h2:2 h3:3 h1:4

// Add entry
hash['h5']=2.5;
order.push('h5');
order.sort(cmp);
// h4:1 h2:2 h5:2.5 h3:3 h1:4

// Delete entry
order.splice(order.indexOf('h5'), 1);
delete hash['h5'];
// h4:1 h2:2 h3:3 h1:4

// Display ordered hash array (with keys)
for(i in order) console.log(order[i],hash[order[i]]);

答案 7 :(得分:0)

采用@Craig_Walker解决方案,如果您只想知道插入属性的顺序,一个简单的解决方案就是:

var obj ={ }
var order = [];

function add(key, value) {
    obj[key] = value;
    order.push(key);
}

function getOldestKey() {
    var key = order.shift();
    return obj[key]
}

function getNewsetKey() {
    var key = order.pop();
    return obj[key]
}

答案 8 :(得分:0)

您现在可以使用本机Map,因为当使用for in循环时,它会保留插入顺序。

答案 9 :(得分:0)

class @OrderedHash

  constructor: (h_as_array=[])->
    @keys = []
    @vals = {}
    if h_as_array.length > 0
      i = 0
      while i < h_as_array.length
        @push(h_as_array[i], h_as_array[i+1])
        i += 2
    @

  push: (k,v)->
    @keys.push k if not @vals[k]
    @vals[k] = v

  length: ()-> return @keys.length
  keys:   ()-> return @keys
  val:   (k)-> return @vals[k]
  vals:   ()-> return @vals
  each: (callback)->
    return unless callback
    for k in @keys
      callback(@vals[k])
相关问题