按值对对象属性进行排序

1008

如果我有一个 JavaScript 对象,例如:

var list = {
  "you": 100, 
  "me": 75, 
  "foo": 116, 
  "bar": 15
};

有没有一种方法可以根据值对属性进行排序?这样我最后会得到:

list = {
  "bar": 15, 
  "me": 75, 
  "you": 100, 
  "foo": 116
};

5
不仅要"排序",更重要的是对数字进行排序。数字不受Javascript的Array.sort()方法影响,这意味着你不仅需要找到一种对属性进行排序的方法,还需要编写自己的函数来比较数值大小。 - Sampson
237
在阅读答案之前:答案是否定的。ECMAScript 中对象属性的顺序是非标准的。您不应该对 JavaScript 对象中元素的顺序做出任何假设。对象是一个无序的属性集合。下面的答案向您展示了如何“使用”已排序的属性,借助于数组的帮助,但实际上从未改变对象本身的属性顺序。所以,不,这是不可能的。即使您构建了具有预排序属性的对象,也不能保证它们将来会以相同的顺序显示。请继续阅读 :)。 - Govind Rai
11
在实际的前端应用程序中,我们使用对象集合的 ID 作为键来循环遍历,并且如果转换为 HTML 模板,则顺序很重要。你说它们没有顺序,我说当我在当前浏览器中使用 console.logging 查看它们时,它们确实有顺序。而且这个顺序可能会被重新排序。一旦你循环遍历它们,它们就会有一个顺序。 - ProblemsOfSumit
9
现在规范中有一种方式可以按指定顺序访问属性。这是一个好主意吗?几乎肯定不是。 :-) 但是它已经存在于ES2015中。 - T.J. Crowder
21
2019年的访客们,可以查看这个仅得到少量赞同的基于Object.entries的答案,它是自ES2017以来最干净、最易读的现代方法:https://dev59.com/LXNA5IYBdhLWcg3wKacx#37607084 - jakub.g
显示剩余5条评论
45个回答

1112
将它们移动到一个数组中,对该数组进行排序,然后将该数组用于你的目的。这是一个解决方案:
let maxSpeed = {
    car: 300, 
    bike: 60, 
    motorbike: 200, 
    airplane: 1000,
    helicopter: 400, 
    rocket: 8 * 60 * 60
};
let sortable = [];
for (var vehicle in maxSpeed) {
    sortable.push([vehicle, maxSpeed[vehicle]]);
}

sortable.sort(function(a, b) {
    return a[1] - b[1];
});

// [["bike", 60], ["motorbike", 200], ["car", 300],
// ["helicopter", 400], ["airplane", 1000], ["rocket", 28800]]

一旦你有了数组,你可以按照自己喜欢的顺序从数组中重新构建对象,从而实现你想要做的事情。这在我所知道的所有浏览器中都可以工作,但它依赖于一个实现上的怪异,并且可能随时出错。你永远不应该对JavaScript对象中元素的顺序做任何假设。
let objSorted = {}
sortable.forEach(function(item){
    objSorted[item[0]]=item[1]
})

在ES8中,你可以使用Object.entries()将对象转换为数组:

const maxSpeed = {
    car: 300, 
    bike: 60, 
    motorbike: 200, 
    airplane: 1000,
    helicopter: 400, 
    rocket: 8 * 60 * 60
};

const sortable = Object.entries(maxSpeed)
    .sort(([,a],[,b]) => a-b)
    .reduce((r, [k, v]) => ({ ...r, [k]: v }), {});

console.log(sortable);


在 ES10 中,你可以使用 Object.fromEntries() 将数组转换为对象。然后代码可以简化为这样:

const maxSpeed = {
    car: 300, 
    bike: 60, 
    motorbike: 200, 
    airplane: 1000,
    helicopter: 400, 
    rocket: 8 * 60 * 60
};

const sortable = Object.fromEntries(
    Object.entries(maxSpeed).sort(([,a],[,b]) => a-b)
);

console.log(sortable);


26
您能否重新表述“你可以重建”为“您可以使用数组来维护键的顺序并从对象中取出值”?正如您自己所提到的,这种非标准的假设不仅在Chrome浏览器中有问题,今天它被更多的浏览器所破坏,因此最好不要鼓励用户尝试它。 - Oleg V. Volkov
34
这是您代码的更紧凑版本。Object.keys(maxSpeed).sort(function(a, b) {return -(maxSpeed[a] - maxSpeed[b])}); (该代码对maxSpeed对象进行处理,返回一个已按照其属性值从大到小排序的键数组。) - TheBrain
7
补充一下,“keys()”仅受IE9+(和其他现代浏览器)支持,如果这是一个问题的话。此外,“keys()”将从元素原型链中排除可枚举属性(不像“for...in”)- 但通常更合适。 - MrWhite
31
_.pairs 将一个对象转换为 [ [key1, value1], [key2, value2] ] 的形式。然后对该数组调用 sort 方法进行排序,然后再使用 _.object 将其转回对象形式。 - dansch
4
ES10的解决方案不起作用,还是我疯了?当我尝试使用它时,Object.fromEntries创建一个按键排序的对象,从而撤销了所有排序工作。 - Chase Denecke
显示剩余14条评论

509

我们不希望复制整个数据结构,也不想使用需要关联数组的数组。

这里有另一种执行与Bonna相同操作的方法:

var list = {"you": 100, "me": 75, "foo": 116, "bar": 15};
keysSorted = Object.keys(list).sort(function(a,b){return list[a]-list[b]})
console.log(keysSorted);     // bar,me,you,foo


34
看起来这是按键而不是值排序,这不符合问题要求的内容? - Michael
42
它按值排序并显示键,但在打印输出时会丢失值计数,因为它只打印键。 - Hanna
8
JavaScript 中不保证对象属性的顺序,因此排序应该在数组中进行,而不是对象(您所称的“关联数组”)。 - Christopher Meyers
9
记住,keysSorted 是一个数组!不是对象! - Green
31
如果在排序的末尾添加.map(key => list[key]);,它将返回整个对象而不仅仅是键。 - James Moran
显示剩余10条评论

253

您的对象可以有任意数量的属性,如果将对象放入数组中,则可以选择按任何对象属性进行排序,无论是数字还是字符串。考虑以下数组:

var arrayOfObjects = [   
    {
        name: 'Diana',
        born: 1373925600000, // Mon, Jul 15 2013
        num: 4,
        sex: 'female'
    },
    {

        name: 'Beyonce',
        born: 1366832953000, // Wed, Apr 24 2013
        num: 2,
        sex: 'female'
    },
    {            
        name: 'Albert',
        born: 1370288700000, // Mon, Jun 3 2013
        num: 3,
        sex: 'male'
    },    
    {
        name: 'Doris',
        born: 1354412087000, // Sat, Dec 1 2012
        num: 1,
        sex: 'female'
    }
];
按出生日期排序,最早的优先。
// use slice() to copy the array and not just make a reference
var byDate = arrayOfObjects.slice(0);
byDate.sort(function(a,b) {
    return a.born - b.born;
});
console.log('by date:');
console.log(byDate);

按名称排序

var byName = arrayOfObjects.slice(0);
byName.sort(function(a,b) {
    var x = a.name.toLowerCase();
    var y = b.name.toLowerCase();
    return x < y ? -1 : x > y ? 1 : 0;
});

console.log('by name:');
console.log(byName);

http://jsfiddle.net/xsM5s/16/


1
按名称排序不应该使用 substr 去掉第一个字符;否则 DianaDebra 将没有定义的顺序。此外,你的 byDate 排序实际上使用的是 num 而不是 born - Lawrence Dol
这看起来不错,但请注意,要比较字符串,您可以直接使用 x.localeCompare(y) - Jemar Jones
2
@JemarJones localCompare 看起来是一个非常有用的函数!只需要注意它不会在每个浏览器中得到支持 - IE 10及以下版本,Safari Mobile 9及以下版本。 - inorganik
@inorganik 对于那些需要支持这些浏览器的人来说,非常好的提示。 - Jemar Jones
1
这是一个对象数组,而不是OP所要求的(具有多个属性的对象)。 - João Pimentel Ferreira
这就是我一直在寻找的。对具有一个属性的JavaScript对象进行排序。 - Keshab

146

ECMAScript 2017 引入了 Object.values / Object.entries。顾名思义,前者将对象的所有值聚合成一个数组,后者将整个对象转换为一个 [键, 值] 数组的数组;类似于 Python 的dict.values()dict.items()

这些特性使得将哈希排序为有序对象变得更加容易。目前只有少部分 JavaScript 平台支持它们,但你可以在 Firefox 47+ 上尝试。

编辑:现在被 所有现代浏览器支持!

let obj = {"you": 100, "me": 75, "foo": 116, "bar": 15};

let entries = Object.entries(obj);
// [["you",100],["me",75],["foo",116],["bar",15]]

let sorted = entries.sort((a, b) => a[1] - b[1]);
// [["bar",15],["me",75],["you",100],["foo",116]]

这怎么可能回答问题的标题“按属性值排序JavaScript对象”?我认为你误解了问题,因为你要改变原始对象而不是从中创建新数组。 - vsync
6
这个答案得到了与被接受的答案相同的结果,但是代码更少且没有临时变量。 - Darren Cook
13
这个答案非常干净,并且是唯一帮助过我的答案。 - NetOperator Wibby
只有在 Firefox 中,而不是 IE 或 Chrome 中,它们才会自动排序对象。真是令人困惑! - f b
1
请注意,这不会保留键。 - Vael Victus
5
为了更进一步简化,将该过程合并成一行代码: let sorted = Object.entries(obj).sort((a, b) => a[1] - b[1]); - Avid

73

为了完整起见,该函数返回对象属性的排序数组

function sortObject(obj) {
    var arr = [];
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            arr.push({
                'key': prop,
                'value': obj[prop]
            });
        }
    }
    arr.sort(function(a, b) { return a.value - b.value; });
    //arr.sort(function(a, b) { return a.value.toLowerCase().localeCompare(b.value.toLowerCase()); }); //use this to sort as strings
    return arr; // returns array
}

var list = {"you": 100, "me": 75, "foo": 116, "bar": 15};
var arr = sortObject(list);
console.log(arr); // [{key:"bar", value:15}, {key:"me", value:75}, {key:"you", value:100}, {key:"foo", value:116}]

这里是包含上述代码的JSFiddle。这个解决方案基于这篇文章

这里是用于字符串排序的更新版fiddle。你可以从中删除所有额外的.toLowerCase()转换,以进行区分大小写的字符串比较。


1
一个对象数组怎么样? [ {name:威尔}, {name:比尔}, {name:本} ] - Will Hancock
1
嗨,威尔,你可以使用localeCompare函数进行比较。已将其添加到上面的答案中。 - Stano
很棒的解决方案。字符串排序需要一个返回。 - pfwd
@Stano 正确而简单的解决方案。非常感谢。 - Abrar Hossain

63

参考 @marcusR 的回答的箭头版本

var myObj = { you: 100, me: 75, foo: 116, bar: 15 };
keysSorted = Object.keys(myObj).sort((a, b) => myObj[a] - myObj[b]);
alert(keysSorted); // bar,me,you,foo

UPDATE: April 2017 This returns a sorted myObj object defined above.

const myObj = { you: 100, me: 75, foo: 116, bar: 15 };
const result =
  Object.keys(myObj)
    .sort((a, b) => myObj[a] - myObj[b])
    .reduce(
      (_sortedObj, key) => ({
        ..._sortedObj,
        [key]: myObj[key]
      }),
      {}
    );
document.write(JSON.stringify(result));

UPDATE: March 2021 - Object.entries with sort function (updated as per comments)

const myObj = { you: 100, me: 75, foo: 116, bar: 15 };
const result = Object
 .entries(myObj)
 .sort((a, b) => a[1] - b[1])
 .reduce((_sortedObj, [k,v]) => ({
   ..._sortedObj, 
   [k]: v
 }), {})
document.write(JSON.stringify(result));


2
无法为 var myObj = {"1": {"Value": 40}, "2": {"Value": 10}, "3": {"Value": 30}, "4": {"Value": 20}}; 运行。 - Rohanil
9
楼主的问题以及这个回答中都没有嵌套对象。@Rohanil也许你可以提出另一个问题,而不是给出负面评价。你的具有各种类型的嵌套对象显然需要比这个解决方案提供的更多内容。 - Jason J. Nathan
只是提醒一下:你的更新实际上不会起作用。至少不是在所有情况下,也不是因为 entries。根据标准,一个对象是属性的无序集合。这意味着,如果您尝试通过属性值或其他任何方式对新对象进行排序构造,属性键的顺序再次变得未定义。例如,Chrome默认按属性键排序,因此每次尝试以不同的方式对它们进行排序都是无用的。您唯一的机会是根据您的排序偏好获取“索引”,并相应地遍历原始对象。 - ZorgoZ
Object.assign()比在reduce中展开_sortedObj要更高效。 - DiegoRBaquero
确认:您的2018年和2020年的解决方案无法正确排序,正如您自己的Ramda REPL链接中所看到的那样。这与Chrome没有任何关系。 - Henke
显示剩余5条评论

39

JavaScript对象在定义上是无序的(详见ECMAScript语言规范第8.6节)。甚至语言规范都不能保证如果你连续两次迭代同一个对象的属性,它们第二次出现的顺序会与第一次相同。

如果您需要有序的东西,请使用数组和Array.prototype.sort方法。


4
注意,关于这个问题已经有很多争论了。大多数实现会按元素添加的顺序保留列表。如果我没记错,Chrome 不会这样做。关于是否让 Chrome 与其他实现保持一致存在争议。我的看法是 JavaScript 对象是一个哈希表,不应该假定任何顺序。我相信 Python 经历了同样的争论,并最近引入了一种新的有序哈希类似列表。对于大多数浏览器,您可以通过重新创建对象,按排序值添加元素来实现您想要的效果。但你不应该这样做。 - Nosredna
8
这份错误报告是不合理的,那些依赖未记录行为的人才是有问题的。请阅读 ECMAScript 第 8.6 节;它清楚地说明“对象是一组无序的属性集合”。任何发现在某些实现中似乎不是这样,并开始依赖于此特定实现行为的人都犯了一个大错误,他们不应该试图把责任推卸给别人。如果我在 Chrome 团队中,我会将该错误报告标记为“无效,不予修复”。 - NickFitz
但是,Chrome为什么要在其他浏览器中正常工作的页面上停止呢?虽然我同意你的想法,但不应该依赖顺序。 - Nosredna
8
EcmaScript 5 实际上定义了枚举顺序为插入顺序--在 ES3 中,缺乏定义被视为规范错误。值得注意的是,EcmaScript 规范定义了一些人们认为不合理的行为,例如规范行为是语法错误在许多情况下会在运行时抛出,continue、break、++、--、const 的使用不正确等。根据规范,任何在到达该代码之前抛出异常的引擎都是“错误”的。 - olliej
5
@olliej - 我不认为这是正确的。规范文件中写道:“枚举属性的机制和顺序(第一个算法中的步骤6.a,第二个算法中的步骤7.a)未指定”。这在最终草案的第92页中有说明。 - whitneyland
显示剩余2条评论

37

好的,可能你已经知道,JavaScript有一个sort()函数可以用来对数组进行排序,但是没有类似的函数可以用来对对象进行排序...

所以在这种情况下,我们需要以某种方式获取键的数组并对它们进行排序,这也是为什么大多数时候API会把对象放在数组中返回,因为Array比对象字面量拥有更多与之配合的原生函数,无论如何,快速的解决方案是使用Object.key函数,它返回一个对象的键的数组。我编写了下面的ES6函数来帮助你完成这个任务,它使用JavaScript中的本地sort()reduce()函数:

function sortObject(obj) {
  return Object.keys(obj)
    .sort().reduce((a, v) => {
    a[v] = obj[v];
    return a; }, {});
}

现在你可以像这样使用它:

let myObject = {a: 1, c: 3, e: 5, b: 2, d: 4};
let sortedMyObject = sortObject(myObject);

查看sortedMyObject,您可以看到按键排序的结果如下:

{a: 1, b: 2, c: 3, d: 4, e: 5}

同样的方式,主要对象不会被更改并且我们实际上得到了一个新的对象。

我还创建了下面这张图片,以使函数步骤更加清晰,以防您需要稍微更改一下来适应您的需求:

按属性值对Javascript对象排序


8
按键排序,而非按值排序。 - ROROROOROROR
@ROROROOROROR,这只是一个指示它的工作方式,因为它们并没有太大的区别,但都很好,我还会添加按值排序的功能。 - Alireza
1
按值排序是错误的,这是由于您的数据集引起的。将 c: 3 更改为 c: 13,您会看到它崩溃了。 - VtoCorleone
2
@VtoCorleone 这只是一种自然排序错误,1排在第一位,并不是算法本身的缺陷。 - Michael Ryan Soileau
1
这正是我正在寻找的,谢谢你。 - respectabiggle

12

使用ES6进行更新:如果您的关注点是拥有一个排序后的对象以便迭代(这就是我想像您需要排序对象属性的原因),则可以使用Map对象。

您可以按排序顺序插入键值对,然后使用for..of循环将保证按照插入顺序进行迭代。

var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (var [key, value] of myMap) {
  console.log(key + " = " + value);
}
// 0 = zero 
// 1 = one

同样在ES6(ES2015)中:对象属性确实有顺序(或者说有一种定义顺序的访问方式,这取决于你的观点)。如果属性名是字符串且不像数组索引,那么它们被添加到对象中的顺序就是它们的顺序。这个顺序没有被指定为for-inObject.keys所遵循,但它被定义为Object.getOwnPropertyNames和其他新方法用于访问属性名称数组时所遵循的顺序。因此,这是另一个选项。 - T.J. Crowder
但是“排序”操作在哪里?它们根本没有被排序。 - Green
@Green 我认为我想要强调的是,使用 Map 实际上可以拥有一个可以按排序顺序存储数据的数据结构(对数据进行排序,循环遍历并将其存储在 Map 中),而使用对象则不能保证这一点。 - julianljk

10
var list = {
    "you": 100, 
    "me": 75, 
    "foo": 116, 
    "bar": 15
};

function sortAssocObject(list) {
    var sortable = [];
    for (var key in list) {
        sortable.push([key, list[key]]);
    }
    // [["you",100],["me",75],["foo",116],["bar",15]]

    sortable.sort(function(a, b) {
        return (a[1] < b[1] ? -1 : (a[1] > b[1] ? 1 : 0));
    });
    // [["bar",15],["me",75],["you",100],["foo",116]]

    var orderedList = {};
    for (var idx in sortable) {
        orderedList[sortable[idx][0]] = sortable[idx][1];
    }

    return orderedList;
}

sortAssocObject(list);

// {bar: 15, me: 75, you: 100, foo: 116}

简单而完美!这个回答了这个问题。谢谢。 - Ajay Singh
在 JavaScript 中,键的顺序不能保证,因此如果键是整数或类似类型,则此方法会失败。 - ParoX
当键是整数类型时,exact和ParoX无法正常工作。 - kakadais

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接