如何在JavaScript中对对象集合进行排序而不将其转换为数组 [英] How to sort a collection of objects in JavaScript without converting it to an array
问题描述
我试图避免针对以下用例编写自己的排序算法:
avatars = {};
avatars[102] = {userInfo: {buddy_name: 'Avatar102', is_online: 1}};
avatars[100] = {userInfo: {buddy_name: 'Avatar100', is_online: 1}};
avatars[101] = {userInfo: {buddy_name: 'Avatar101', is_online: 1}};
console.log(_.keys(avatars));
avatars = _.sortBy(avatars, function(avatar) {return avatar.userInfo.buddy_name.toLowerCase();});
console.log(_.keys(avatars));
这是控制台输出:
- ["102","100","101"]
- ["0","1","2"]
如您所见,使用undescore的sortBy,我丢失了关键数据.这个结构可能会变得非常大,所以我试图避免诸如转换为数组然后再返回到集合之类的事情.有什么方法可以不用滚动我自己的排序功能吗?
您的avatars
不是数组,而只是一个对象:
avatars = {};
因此,其元素没有定义的顺序:
未指定枚举属性的机制和顺序(第一个算法中的步骤6.a,第二个算法中的步骤7.a).
如果实现为for-in语句定义了特定的枚举顺序,则必须使用相同的枚举顺序对此算法的第3步中的列表元素进行排序.
您还可以查看第8.6 节,以查看是否有关于对象中属性顺序的任何提及.对象属性排序的唯一要求是,如果实现在任何地方都定义了一个顺序,则它必须在任何地方都使用相同的排序,但这是一个很大的 if .大多数实现可能都将插入顺序用于对象的键,但是我找不到任何需要它们插入的东西(如果有人可以在规范中指出定义对象键的任何特定顺序的任何内容,我将不胜感激).>
也就是说,Underscore的 sortBy
基本上是 sort
和Underscore的 pluck
来解开Schwartzian Transform备忘录包装; pluck
返回一个数组,所以sortBy
也返回一个数组.因此,您最后的_.keys(avatars)
调用实际上是在数组上调用 _.keys
;数组的键(又称可枚举属性)是数组的索引,并且是从零开始的连续整数.
您使用了错误的数据结构.如果您需要稀疏数组但还需要像数组一样对其进行操作(即对它进行排序),则应将索引放在对象中并使用普通数组和pluck
而不是keys
:
var avatars = [
{idx: 102, userInfo: {buddy_name: 'Avatar102', is_online: 1}},
{idx: 100, userInfo: {buddy_name: 'Avatar100', is_online: 1}},
{idx: 101, userInfo: {buddy_name: 'Avatar101', is_online: 1}}
];
console.log(_(avatars).pluck('idx'));
avatars = _(avatars).sortBy(function(avatar) {
return avatar.userInfo.buddy_name.toLowerCase();
});
console.log(_(avatars).pluck('idx'));
演示: http://jsfiddle.net/ambiguous/UCWL2/
如果您还需要通过idx
快速访问,则可以为直接idx
访问设置并行对象:
var avatars_by_idx = { };
for(var i = 0; i < avatars.length; ++i)
avatars_by_idx[avatars[i].idx] = avatars[i];
然后avatars_by_idx
提供您正在寻找的直接访问权限.当然,您必须保持avatars
和avatars_by_idx
同步,但是如果将它们都隐藏在一个对象后面,这并不是很困难.
I am trying to avoid writing my own sorting algorithm for the following use case:
avatars = {};
avatars[102] = {userInfo: {buddy_name: 'Avatar102', is_online: 1}};
avatars[100] = {userInfo: {buddy_name: 'Avatar100', is_online: 1}};
avatars[101] = {userInfo: {buddy_name: 'Avatar101', is_online: 1}};
console.log(_.keys(avatars));
avatars = _.sortBy(avatars, function(avatar) {return avatar.userInfo.buddy_name.toLowerCase();});
console.log(_.keys(avatars));
Here's the console output:
- ["102", "100", "101"]
- ["0", "1", "2"]
As you can see, with undescore's sortBy I am losing the key data. This struct can get very large, so I am trying to avoid things like converting to an array and then back to the collection. Is there any way to do this without rolling my own sort function?
Your avatars
is not an Array, it is just an object:
avatars = {};
so there is no defined order for its elements:
The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified.
If an implementation defines a specific order of enumeration for the for-in statement, that same enumeration order must be used to order the list elements in step 3 of this algorithm.
You can also check section 8.6 to see if there is any mention about the order of properties in an object. The only requirement for ordering of object properties is that if the implementation defines an order anywhere then it has to use that same ordering everywhere but that's a big if. Most implementations probably use insertion order for an object's keys but I can't find anything that requires them to (I'd appreciate a comment if anyone can point out anything in the specs that define any particular order of an object's keys).
That said, Underscore's sortBy
is basically a Schwartzian Transform combined with a standard JavaScript sort
and Underscore's pluck
to unwrap the Schwartzian Transform memo wrappers; pluck
returns an array so sortBy
also returns an array. Hence, your final _.keys(avatars)
call is actually calling _.keys
on an array; the keys of an array (AKA enumerable properties) are the array's indices and those are consecutive integers starting at zero.
You're using the wrong data structure. If you need a sparse array but also need to manipulate it like an array (i.e. sort it), then you should put the indexes inside the objects and use a normal array and pluck
instead of keys
:
var avatars = [
{idx: 102, userInfo: {buddy_name: 'Avatar102', is_online: 1}},
{idx: 100, userInfo: {buddy_name: 'Avatar100', is_online: 1}},
{idx: 101, userInfo: {buddy_name: 'Avatar101', is_online: 1}}
];
console.log(_(avatars).pluck('idx'));
avatars = _(avatars).sortBy(function(avatar) {
return avatar.userInfo.buddy_name.toLowerCase();
});
console.log(_(avatars).pluck('idx'));
Demo: http://jsfiddle.net/ambiguous/UCWL2/
If you also need quick access by idx
then you could set up a parallel object for direct idx
access:
var avatars_by_idx = { };
for(var i = 0; i < avatars.length; ++i)
avatars_by_idx[avatars[i].idx] = avatars[i];
Then avatars_by_idx
provides the direct access you're looking for. Of course, you'd have to keep avatars
and avatars_by_idx
synchronized but that's not terribly difficult if you hide them both behind an object.
这篇关于如何在JavaScript中对对象集合进行排序而不将其转换为数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!