按键排序JavaScript对象

839
我需要按键名对JavaScript对象进行排序。
因此以下内容:
{ 'b' : 'asdsad', 'c' : 'masdas', 'a' : 'dsfdsfsdf' }

将变成:

{ 'a' : 'dsfdsfsdf', 'b' : 'asdsad', 'c' : 'masdas' }

3
可能是重复的问题:在Javascript中对JSON对象进行排序 - abraham
71
在2011年(发布此问题时),ECMAScript规范指出JavaScript对象没有内在顺序——观察到的顺序取决于具体实现,因此不能依赖它。然而,事实证明所有JavaScript引擎都实现了更多或更少相同的语义,因此这些语义现在已在ES6中标准化。因此,许多下面的答案过去是正确的,符合规范,但现在不再正确。 - Mathias Bynens
1
简单来说,当您需要比较或哈希结果时,请使用排序的 stringify:https://www.npmjs.com/package/json-stable-stringify - exebook
7
请注意,自从 Object.entriesObject.fromEntries 被添加到 JS 中以来,可以使用一个漂亮的一行代码实现此操作:Object.fromEntries(Object.entries(obj).sort()) - Mike 'Pomax' Kamermans
递归按键对JavaScript对象进行排序 - https://dev59.com/64Hba4cB1Zd3GeqPPlKn#73160202 - 即对对象及其包含的对象进行排序 - danday74
显示剩余5条评论
38个回答

813

这个问题的其他答案已经过时,从未匹配实现现实,并且现在官方上已经因为ES6/ ES2015规范的发布而被正式视为不正确。


请参阅Axel Rauschmayer的 Exploring ES6 关于属性迭代顺序的章节:

所有迭代属性键的方法都按相同的顺序执行:

  1. 首先是所有数组索引,按数字排序。
  2. 然后是所有字符串键(不是索引),按创建顺序排列。
  3. 最后是所有符号,按创建顺序排列。

因此,JavaScript对象实际上是有序的,它们的键/属性的顺序可以更改。

以下是如何按字母顺序对对象的键/属性进行排序:

const unordered = {
  'b': 'foo',
  'c': 'bar',
  'a': 'baz'
};

console.log(JSON.stringify(unordered));
// → '{"b":"foo","c":"bar","a":"baz"}'

const ordered = Object.keys(unordered).sort().reduce(
  (obj, key) => { 
    obj[key] = unordered[key]; 
    return obj;
  }, 
  {}
);

console.log(JSON.stringify(ordered));
// → '{"a":"baz","b":"foo","c":"bar"}'

使用 var 替代 const,以确保与 ES5 引擎兼容。


10
除了使用略有不同的循环技巧外,我真的看不出你的代码和这里的最佳答案有什么区别。无论这些对象是否被认为是“有序”的似乎都是不相关的。对于这个问题的答案仍然是相同的。发布的规范只是确认了这里的答案。 - Phil
7
FYI,已经澄清了属性的枚举顺序仍然未定义:https://esdiscuss.org/topic/property-ordering-of-enumerate-getownpropertynames - Felix Kling
9
@Phil_1984_,这个答案和得票最高的答案非常不同。OP的问题是如何对一个对象字面量进行排序。得票最高的答案说你不能做到,然后提供了一种通过在数组中存储排序后的键,然后迭代并从排序后的数组中打印键值对的方法来解决。这个答案声称对象字面量的顺序现在是可排序的,他的示例将排序后的键值存回到对象字面量中,然后直接打印排序后的对象。顺便说一下,如果链接的网站仍然存在,那就太好了。 - cchamberlain
6
两个答案都简单地使用Array.sort函数先对键进行排序。OP正在问如何对“JavaScript对象”进行排序,仅仅是使用JavaScript对象表示法(JSON)来描述这两个对象。这两个对象在逻辑上是完全相同的,这意味着排序是无关紧要的。我认为排名第一的回答更好地解释了这一点。说“所有其他答案现在正式不正确”对我来说太过极端了,尤其是由于一个错误的链接。我认为问题是“这个新的ES6规范”给人们一种假象,即JavaScript对象有一个有序的键列表,这是不正确的。 - Phil
2
请添加说明,此解决方案不受多种现代浏览器的支持(请搜索“caniuse ES6”)。使用此答案会在某些浏览器中存在难以发现的错误风险(例如,当Chrome正常时,Safari可能出现问题)。 - Manuel Arwed Schmidt
显示剩余13条评论

262

JavaScript对象1是无序的。尝试"排序"它们是没有意义的。如果你想遍历一个对象的属性,你可以对键进行排序,然后检索相应的值:

var myObj = {
    'b': 'asdsadfd',
    'c': 'masdasaf',
    'a': 'dsfdsfsdf'
  },
  keys = [],
  k, i, len;

for (k in myObj) {
  if (myObj.hasOwnProperty(k)) {
    keys.push(k);
  }
}

keys.sort();

len = keys.length;

for (i = 0; i < len; i++) {
  k = keys[i];
  console.log(k + ':' + myObj[k]);
}


使用Object.keys函数的另一种实现方式:


var myObj = {
    'b': 'asdsadfd',
    'c': 'masdasaf',
    'a': 'dsfdsfsdf'
  },
  keys = Object.keys(myObj),
  i, len = keys.length;

keys.sort();

for (i = 0; i < len; i++) {
  k = keys[i];
  console.log(k + ':' + myObj[k]);
}


1虽不想显得过于学究,但是JSON对象并不存在


4
JSON对象是存在的,可以查看官方JSON RFC第8节:http://www.ietf.org/rfc/rfc4627.txt。 - Paul
10
关于那个RFC,有两件事需要注意:首先,它已经有7年了,词汇随着时间而变化;其次,“此备忘录为互联网社区提供信息。它不指定任何类型的互联网标准。”无论给定的字符序列表示JavaScript对象文字还是JSON文本都取决于上下文/用法。术语“JSON对象”混淆并使区别模糊,这是有害的。让我们摆脱不精确的术语,在可能的情况下使用更精确的术语。我认为没有理由做其他事情。话语很重要。 - Matt Ball
7
在我看来,“JSON对象”是一个非常准确的术语,用于描述像{"a", 1}这样的字符串,就像“JSON数组”是指[1]这样的字符串。当你使用字符串[{x: 1}, {y: 2}, {z: 3}]时,能够说出“我的数组中的第三个对象”是很有用的。因此,当评论JavaScript字面量时,我更喜欢使用“那不是一个JSON对象”,而不是“不存在JSON对象”,后者只会在OP实际处理JSON时导致更多的混乱和沟通困难。 - Paul
3
@Paulpro,我必须表示不同意。{"a", 1}可以是对象字面量或JSON文本(如果你愿意的话也可以称为JSON字符串)。它是哪一个取决于上下文,如果它在JavaScript源代码中直接出现,则为前者;如果它是需要传递给JSON解析器以进一步使用的字符串,则为后者。在允许的语法、正确的用法和序列化方面,这两者之间确实存在真正的差异。 - Matt Ball
9
现在ES6/ES2015已经确定下来,因此这个答案已经正式过时。请参考我的答案了解更多信息。 - Mathias Bynens
显示剩余10条评论

249
许多人提到“对象不能被排序”,但是之后他们会给出可行的解决方案。这是个悖论,不是吗?
没有人提到为什么这些解决方案有效。它们有效是因为大多数浏览器实现中,对象中的值按照添加的顺序存储。这就是为什么如果您从已排序的键列表创建新的对象,则返回预期的结果。
我认为我们可以再添加一种解决方案——ES5函数式方法:
function sortObject(obj) {
    return Object.keys(obj).sort().reduce(function (result, key) {
        result[key] = obj[key];
        return result;
    }, {});
}

以上为ES2015版本(格式化成“单行”):

const sortObject = o => Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {})

上面示例的简要说明(根据评论要求):

Object.keys给我们提供了一个提供对象(objo)中键的列表,然后我们使用默认排序算法对这些键进行排序,接下来使用.reduce将该数组转换回对象,但这次所有键都已排序。


10
这种行为在所有主要浏览器中一直都可用,并且已经在ES6/ES2015中进行了标准化。我认为这是一个好建议。 - Mathias Bynens
4
这是在EcmaScript v5中完成任务最简单、最简洁的方法。虽然有点晚了,但应该被认为是正确的答案。 - Steven de Salas
2
它们是这样的,因为在大多数浏览器实现中,对象中的值按照添加的顺序存储,但仅适用于非数字属性。但请注意,无法保证以哪种顺序迭代属性(例如通过 for...in)。 - Felix Kling
1
真正的一句话代码:const sortObject = o => Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {}) - cchamberlain
3
谢谢你解释了为什么它能够起作用,这对我来说很有帮助。谢谢! - ola
9
我的代码检查器抱怨了那个一行代码(返回一个赋值),但是这个对我来说可行且更容易阅读:Object.keys(dict).sort().reduce((r, k) => Object.assign(r, { [k]: dict[k] }), {}); - aks.

100

各位,我感到震惊!虽然所有的答案都有点老,但没有人提到排序中的稳定性!所以请耐心等待,我会尽力回答问题并在此详细说明。因此,我现在要道歉,这将是一篇很长的阅读。

由于现在是2018年,我将只使用ES6,Polyfills都可以在MDN文档中找到,我会在相应部分提供链接。


问题的答案:

如果你的键只是数字,那么你可以安全地使用Object.keys()Array.prototype.reduce()来返回排序后的对象:

// Only numbers to show it will be sorted.
const testObj = {
  '2000': 'Articel1',
  '4000': 'Articel2',
  '1000': 'Articel3',
  '3000': 'Articel4',
};

// I'll explain what reduces does after the answer.
console.log(Object.keys(testObj).reduce((accumulator, currentValue) => {
  accumulator[currentValue] = testObj[currentValue];
  return accumulator;
}, {}));

/**
 * expected output:
 * {
 * '1000': 'Articel3',
 * '2000': 'Articel1',
 * '3000': 'Articel4',
 * '4000': 'Articel2' 
 *  } 
 */

// if needed here is the one liner:
console.log(Object.keys(testObj).reduce((a, c) => (a[c] = testObj[c], a), {}));

然而,如果你正在处理字符串,我强烈建议将Array.prototype.sort()链接到所有这些操作中:

// String example
const testObj = {
  'a1d78eg8fdg387fg38': 'Articel1',
  'z12989dh89h31d9h39': 'Articel2',
  'f1203391dhj32189h2': 'Articel3',
  'b10939hd83f9032003': 'Articel4',
};
// Chained sort into all of this.
console.log(Object.keys(testObj).sort().reduce((accumulator, currentValue) => {
  accumulator[currentValue] = testObj[currentValue];
  return accumulator;
}, {}));

/**
 * expected output:   
 * { 
 * a1d78eg8fdg387fg38: 'Articel1',
 * b10939hd83f9032003: 'Articel4',
 * f1203391dhj32189h2: 'Articel3',
 * z12989dh89h31d9h39: 'Articel2' 
 * }
 */

// again the one liner:
console.log(Object.keys(testObj).sort().reduce((a, c) => (a[c] = testObj[c], a), {}));

如果有人想知道reduce的作用:

// Will return Keys of object as an array (sorted if only numbers or single strings like a,b,c).
Object.keys(testObj)

// Chaining reduce to the returned array from Object.keys().
// Array.prototype.reduce() takes one callback 
// (and another param look at the last line) and passes 4 arguments to it: 
// accumulator, currentValue, currentIndex and array
.reduce((accumulator, currentValue) => {

  // setting the accumulator (sorted new object) with the actual property from old (unsorted) object.
  accumulator[currentValue] = testObj[currentValue];

  // returning the newly sorted object for the next element in array.
  return accumulator;

  // the empty object {} ist the initial value for  Array.prototype.reduce().
}, {});

如果需要,这里是一行代码的解释:
Object.keys(testObj).reduce(

  // Arrow function as callback parameter.
  (a, c) => 

  // parenthesis return! so we can safe the return and write only (..., a);
  (a[c] = testObj[c], a)

  // initial value for reduce.
  ,{}
);

为什么排序有点复杂:

简而言之,Object.keys()将返回一个与普通循环得到的顺序相同的数组:

const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

console.log(Object.keys(object1));
// expected output: Array ["a", "b", "c"]

Object.keys() 返回一个数组,其元素是字符串,对应于直接在对象上找到的可枚举属性。属性的顺序与手动循环对象属性时给定的顺序相同。

顺便提一下 - 你也可以在数组上使用Object.keys(),但要记住返回的是索引:

// simple array
const arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']

但实际上并不像示例所展示的那样简单,现实世界的对象可能包含数字、字母或甚至符号(请勿这样做)。

以下是一个包含所有这些元素的对象示例:

// This is just to show what happens, please don't use symbols in keys.
const testObj = {
  '1asc': '4444',
  1000: 'a',
  b: '1231',
  '#01010101010': 'asd',
  2: 'c'
};

console.log(Object.keys(testObj));
// output: [ '2', '1000', '1asc', 'b', '#01010101010' ]

现在,如果我们在上面的数组上使用Array.prototype.sort(),输出会发生变化:
console.log(Object.keys(testObj).sort());
// output: [ '#01010101010', '1000', '1asc', '2', 'b' ]

以下是文档中的引用:

sort()方法会就地对数组元素进行排序,并返回该数组。排序不一定是稳定的。默认的排序顺序是按照字符串Unicode代码点的顺序。

排序的时间和空间复杂度不能保证,因为它取决于具体实现。

你必须确保其中一个返回所需的输出。在现实生活中的例子中,人们往往会混淆事情,尤其是当你同时使用不同的信息输入,如API和数据库时。


那么这有什么大不了的?

嗯,有两篇文章是每个程序员都应该理解的:

原地算法

在计算机科学中,原地算法是一种不使用辅助数据结构来转换输入的算法。但是允许为辅助变量使用少量额外存储空间。随着算法执行,通常会将输出覆盖输入。原地算法仅通过替换或交换元素来更新输入序列。未进行原地处理的算法有时被称为非原地或就地外部算法。

所以基本上我们的旧数组将被覆盖!如果您想因其他原因保留旧数组,则这很重要。请记住这一点。

排序算法

稳定排序算法按照相同顺序排序相同的元素,就像它们在输入中出现的顺序一样。当对某些类型的数据进行排序时,只有部分数据在确定排序顺序时被检查。例如,在右侧的卡牌排序示例中,正在按其等级对卡牌进行排序,并忽略其花色。这允许多个不同的正确排序版本的原始列表。稳定的排序算法根据以下规则选择其中之一:如果两个项目相等(如两个5卡),则它们的相对顺序将被保留,以便如果一个在输入中出现在另一个之前,则它也将在输出中出现在另一个之前。

enter image description here

一种对扑克牌进行稳定排序的示例。当使用稳定排序按等级对牌进行排序时,两个5必须保持与原始顺序相同的顺序出现在排序后的输出中。当使用非稳定排序进行排序时,5可能以相反的顺序出现在排序后的输出中。这表明排序是正确的,但它发生了变化。因此,在现实世界中,即使排序是正确的,我们也必须确保我们得到了预期的结果!这非常重要,请记住这一点。有关更多JavaScript示例,请查看Array.prototype.sort() - docs:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

8
你真是太棒了!感谢你提供了这么好的答案。我在 Stack Overflow 上看过的最好的答案之一。干杯! - Gavin
如果你能在最后提供一个可用的方法就好了。不过回答很棒! - mjs
Array.prototype.sort() 的文档说明它在所有现代浏览器中都是稳定的。这是否意味着从示例中排序卡片的结果将始终相同? - Arthur Stankevich
谢谢@Megajin,这非常有价值。 - Arthur Stankevich
如果我们想按数字键进行降序排序怎么办?例如,首先是“4000”,然后是“3000”,“2000”...? - ivan milenkovic
显示剩余9条评论

81

现在是2023年,我们有一种解决这个问题的方法 :)

Object.fromEntries(Object.entries({b: 3, a:8, c:1}).sort())

2
我们如何按键对嵌套对象进行排序? - arunmmanoharan
不错的一行代码,但如何以不区分大小写的方式对键进行排序? - theMaxx
2
@theMaxx 使用自定义函数进行排序,类似于:Object.fromEntries(Object.entries({b: 3, a:8, c:1}).sort(([key1, val1], [key2, val2]) => key1.toLowerCase().localeCompare(key2.toLowerCase()))) - Ben
这个解决方案很笨重 - 它将条目数组转换为字符串。而且对于任意键都不起作用,例如 Object.entries({ a: 8, ['a,1']: 6 }).sort() -> [ [ 'a,1', 6 ], [ 'a', 8 ] ]  - xmedeko
1
这将通过将条目转换为字符串来进行排序,如果我们的键具有,字符,则结果顺序将受到影响。Object.fromEntries(Object.entries({'a.': 1, 'a': 2, 'a,': 234}).sort()) - {a,: 234, a: 2, a.: 1},至少它不会是ASCII顺序。 - Klesun
显示剩余5条评论

38

ES6-这里是一行代码

var data = { zIndex:99,
             name:'sravan',
             age:25, 
             position:'architect',
             amount:'100k',
             manager:'mammu' };

console.log(Object.entries(data).sort().reduce( (o,[k,v]) => (o[k]=v,o), {} ));


4
这是什么疯狂的语法? (o[k] = v, o)。为什么它能够工作,我在哪里可以找到相关文档?显然它返回最右边的参数,但为什么呢? - ntaso
1
请点击此处查看 @ntaso 的内容(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Comma_Operator)。 - James
短函数首先使 o[k] 等于 v,然后返回 o - Tahsin Turkoz
这个解决方案很笨重 - 它将条目数组转换为字符串。而且对于任意键都不起作用,例如 Object.entries({ a: 8, ['a,1']: 6 }).sort() -> [ [ 'a,1', 6 ], [ 'a', 8 ] ]。 - xmedeko

29

这对我有效

/**
 * Return an Object sorted by it's Key
 */
var sortObjectByKey = function(obj){
    var keys = [];
    var sorted_obj = {};

    for(var key in obj){
        if(obj.hasOwnProperty(key)){
            keys.push(key);
        }
    }

    // sort keys
    keys.sort();

    // create new array based on Sorted Keys
    jQuery.each(keys, function(i, key){
        sorted_obj[key] = obj[key];
    });

    return sorted_obj;
};

10
这实际上是对手头问题的正确答案。如果需要使用Underscore来得到一个简化版本,请看这里:http://jsfiddle.net/wPjaQ/1/ - radicand
7
不,这个答案是不正确的。它返回一个新对象,该对象也没有顺序。 - Paul
3
在实践中也是如此。对象键上不存在所谓的顺序,那么你怎么能说这样做可以让你得到一个按排序顺序排列的对象呢?它只是返回传入对象的一个浅拷贝。 - Paul
9
这个做法非常错误。虽然它现在可能能够工作,但是它会在不同的网络浏览器或者相同浏览器中加载不同的页面时出错。浏览器为了安全起见可能会随机排列对象键,或者当你填充一个对象时,它会根据键值将值放入不同的内存桶中,这样就会返回不同的顺序。如果这种方法正好奏效,那只是你幸运罢了。 - Yobert
16
它还不必要地使用了 jQuery。 - RobG
显示剩余4条评论

24

这是一个老问题,但借鉴Mathias Bynens的回答,我做了一个简短的版本来对 当前 对象进行排序,没有太多额外的开销。

    Object.keys(unordered).sort().forEach(function(key) {
        var value = unordered[key];
        delete unordered[key];
        unordered[key] = value;
    });

在代码执行后,“unordered”对象本身的键将按字母顺序排序。


1
天哪,正是我在寻找的。当对对象进行操作时,我通常更喜欢通过引用对它们进行操作,因此返回新对象的解决方案并不理想。非常感谢你。来世我欠你一杯咖啡/茶/啤酒/拥抱 :-P - onassar

21
假设这在 VisualStudio 调试器中非常有用,因为它可以显示无序的对象属性。
(function(s) {
    var t = {};

    Object.keys(s).sort().forEach(function(k) {
        t[k] = s[k]
    });

    return t
})({
    b: 2,
    a: 1,
    c: 3
});

与内联版本相同:
(function(s){var t={};Object.keys(s).sort().forEach(function(k){t[k]=s[k]});return t})({b:2,a:1,c:3})

非常感谢,谢谢! - Mugiwara
很好!感谢您提供这个简明的解决方案。 - Con Antonakos

20

使用lodash,这将起作用:

some_map = { 'b' : 'asdsad', 'c' : 'masdas', 'a' : 'dsfdsfsdf' }

// perform a function in order of ascending key
_(some_map).keys().sort().each(function (key) {
  var value = some_map[key];
  // do something
});

// or alternatively to build a sorted list
sorted_list = _(some_map).keys().sort().map(function (key) {
  var value = some_map[key];
  // return something that shall become an item in the sorted list
}).value();

只是思考的食物。


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