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