如何按多个字段对对象数组进行排序?

Posted

技术标签:

【中文标题】如何按多个字段对对象数组进行排序?【英文标题】:How to sort an array of objects by multiple fields? 【发布时间】:2011-10-18 07:35:19 【问题描述】:

从这个original question,我将如何对多个字段应用排序?

使用这个稍微调整的结构,我将如何排序城市(升序)然后价格(降序)?

var homes = [
    "h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500",
    "h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250",
    "h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699",
    "h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"
    ];

我喜欢这个事实,而不是提供了一个通用方法的 answer。在我计划使用此代码的地方,我将不得不对日期和其他事情进行排序。 “启动”对象的能力似乎很方便,如果不是有点麻烦的话。

我尝试将这个answer 构建成一个很好的通用示例,但我运气不佳。

【问题讨论】:

您要搜索还是排序? 您在使用您链接的第二个答案时遇到了什么问题? 不够通用。当我只想说sort(["first-field", "ASC"], ["second-field", "DSC"]); 时,我似乎添加了一大堆代码当我尝试添加第一个答案的“入门”逻辑以便我可以处理日期、不区分大小写等时,这更加复杂。 或者您可以对每个字段应用权重 你可以检查lodash.com/docs/4.17.11#orderBy,如果你可以使用lodash 【参考方案1】:

对于您的确切问题的非通用、简单的解决方案:

homes.sort(
   function(a, b)           
      if (a.city === b.city) 
         // Price is only important when cities are the same
         return b.price - a.price;
      
      return a.city > b.city ? 1 : -1;
   );

【讨论】:

我认为这个演示是 OP 想要的 => jsfiddle.net/zJ6UA/533 这个思路是对的,但是逻辑全错了。你不能从另一个字符串中减去一个非数字字符串,if 语句没有意义。 您可以在最后一行使用a.localeCompare(b) 进行字符串比较... see the docs 第一个城市比较不应该检查平等,而不是不平等吗?换句话说,该行不应该是if (a.city === b.city)吗?也就是说,如果两个城市相同,则比较价格,否则比较城市。 非常优雅。如果 javascript 有一个 sortBy 和一个如下的 thenSortBy 就像 LINQ 一样,那就太好了。【参考方案2】:

您可以使用链式排序方法,获取值的增量,直到它达到不等于零的值。

var data = [ h_id: "3", city: "Dallas", state: "TX", zip: "75201", price: "162500" ,  h_id: "4", city: "Bevery Hills", state: "CA", zip: "90210", price: "319250" ,  h_id: "6", city: "Dallas", state: "TX", zip: "75000", price: "556699" ,  h_id: "5", city: "New York", state: "NY", zip: "00010", price: "962500" ];

data.sort(function (a, b) 
    return a.city.localeCompare(b.city) || b.price - a.price;
);

console.log(data);
.as-console-wrapper  max-height: 100% !important; top: 0; 

或者,使用 es6,简单地说:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);

【讨论】:

我错过了什么吗?为什么要使用 60 行代码来完成可以在 1 中完成的事情。简单、清晰、简洁。应该是国际海事组织接受的答案。 现在 SO 的一个大问题是旧答案 - 通常被使用新语言功能(例如 ES5-6-7)的更好解决方案很好地取代,保持旧分数,我们都必须滚动下来寻找“真正的”最佳解决方案! SO 应该随着时间的推移使投票过期来解决这个问题,因为随着时间的推移问题会变得越来越严重。 @AndyLorenz 完全同意。解决这个问题的方法很多。例如,当有超过 y 个答案高于 z 评级时,用户设置以最小化超过 x 年的答案。一个更简单的开始是在排序按钮中添加一个“最新”选项。 结合另一个答案以满足我的需要 - a - z 多个属性:return a.status.localeCompare(b.status) || a.name > b.name ? 1 : a.name < b.name ? -1 : 0; 这是一个很好的答案——非常简洁!也许值得解释它是有效的,因为零 - 当两个值匹配时由 localeCompare() 返回 - 是错误的,而 -1 和 +1 是真实的。【参考方案3】:

一种多维排序方法,based on this answer:

更新:这是一个“优化”版本。它做了更多的预处理,并预先为每个排序选项创建了一个比较函数。它可能需要更多内存(因为它为每个排序选项存储一个函数,但它应该执行得更好一些,因为它不必在比较期间确定正确的设置。不过我还没有进行任何分析。

var sort_by;

(function() 
    // utility functions
    var default_cmp = function(a, b) 
            if (a == b) return 0;
            return a < b ? -1 : 1;
        ,
        getCmpFunc = function(primer, reverse) 
            var dfc = default_cmp, // closer in scope
                cmp = default_cmp;
            if (primer) 
                cmp = function(a, b) 
                    return dfc(primer(a), primer(b));
                ;
            
            if (reverse) 
                return function(a, b) 
                    return -1 * cmp(a, b);
                ;
            
            return cmp;
        ;

    // actual implementation
    sort_by = function() 
        var fields = [],
            n_fields = arguments.length,
            field, name, reverse, cmp;

        // preprocess sorting options
        for (var i = 0; i < n_fields; i++) 
            field = arguments[i];
            if (typeof field === 'string') 
                name = field;
                cmp = default_cmp;
            
            else 
                name = field.name;
                cmp = getCmpFunc(field.primer, field.reverse);
            
            fields.push(
                name: name,
                cmp: cmp
            );
        

        // final comparison function
        return function(A, B) 
            var a, b, name, result;
            for (var i = 0; i < n_fields; i++) 
                result = 0;
                field = fields[i];
                name = field.name;

                result = field.cmp(A[name], B[name]);
                if (result !== 0) break;
            
            return result;
        
    
());

使用示例:

homes.sort(sort_by('city', name:'price', primer: parseInt, reverse: true));

DEMO


原功能:

var sort_by = function() 
   var fields = [].slice.call(arguments),
       n_fields = fields.length;

   return function(A,B) 
       var a, b, field, key, primer, reverse, result, i;

       for(i = 0; i < n_fields; i++) 
           result = 0;
           field = fields[i];

           key = typeof field === 'string' ? field : field.name;

           a = A[key];
           b = B[key];

           if (typeof field.primer  !== 'undefined')
               a = field.primer(a);
               b = field.primer(b);
           

           reverse = (field.reverse) ? -1 : 1;

           if (a<b) result = reverse * -1;
           if (a>b) result = reverse * 1;
           if(result !== 0) break;
       
       return result;
   
;

DEMO

【讨论】:

为了记录,这个函数仍然可以通过预处理参数列表并创建一个统一的“排序选项数组”来改进。这留给读者练习;) @Mike: Ok... finally ;) 你现在看到它更复杂了,因为选项是经过预处理的,但最终的比较函数(见评论)要简单得多(希望)导致更好的性能。您拥有的排序选项越多,您从这种方法中获得的优势就越大。【参考方案4】:

这是一个简单的函数式通用方法。使用数组指定排序顺序。在前面加上 minus 以指定降序。

var homes = [
    "h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":"162500",
    "h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250",
    "h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699",
    "h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"
    ];

homes.sort(fieldSorter(['city', '-price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) 
    return function (a, b) 
        return fields
            .map(function (o) 
                var dir = 1;
                if (o[0] === '-') 
                   dir = -1;
                   o=o.substring(1);
                
                if (a[o] > b[o]) return dir;
                if (a[o] < b[o]) return -(dir);
                return 0;
            )
            .reduce(function firstNonZeroValue (p,n) 
                return p ? p : n;
            , 0);
    ;

编辑:在 ES6 中更短!

"use strict";
const fieldSorter = (fields) => (a, b) => fields.map(o => 
    let dir = 1;
    if (o[0] === '-')  dir = -1; o=o.substring(1); 
    return a[o] > b[o] ? dir : a[o] < b[o] ? -(dir) : 0;
).reduce((p, n) => p ? p : n, 0);

const homes = ["h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":162500,     "h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":319250,"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":556699,"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":962500];
const sortedHomes = homes.sort(fieldSorter(['state', '-price']));

document.write('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')

【讨论】:

我发现这个函数非常简洁,因此根据解析器的不同,我对性能进行了高达 90% 的小幅提升。我做了一个gist 和test suite。 根据示例数据,看起来数字按预期排序,但是当我尝试实现这些数字时,排序更像字符串...[10,100,11,9]。我错过了什么吗? @MarkCarpenterJr。不明白你的意思。我的示例正确排序数字类型。您能否将您的实现作为一个问题分享并在 cmets 中引用我以便我看到?然后我可以检查。 @MarkCarpenterJr。刚发现。我在 cmets 中添加了解释。【参考方案5】:

我今天做了一个非常通用的多特征分类器。您可以在这里查看 thenBy.js:https://github.com/Teun/thenBy.js

它允许您使用标准的 Array.sort,但具有 firstBy().thenBy().thenBy() 样式。与上面发布的解决方案相比,它的代码和复杂性要少得多。

【讨论】:

好吧,当您调用 3 次时,对于第二次调用没有影响的项目,不能保证第二次调用不会影响第一次调用的顺序。【参考方案6】:

以下函数将允许您对一个或多个属性的对象数组进行排序,按升序(默认)或降序对每个属性进行排序,并允许您选择是否执行区分大小写的比较。默认情况下,此函数执行不区分大小写的排序。

第一个参数必须是包含对象的数组。 随后的参数必须是一个逗号分隔的字符串列表,这些字符串引用不同的对象属性进行排序。最后一个参数(可选)是一个布尔值,用于选择是否执行区分大小写的排序 - 使用 true 进行区分大小写的排序。

默认情况下,该函数将按升序对每个属性/键进行排序。如果您希望特定键按降序排序,则改为以这种格式传入一个数组:['property_name', true]

以下是该函数的一些示例用法,然后是说明(其中homes 是一个包含对象的数组):

objSort(homes, 'city') --> 按城市排序(升序,不区分大小写)

objSort(homes, ['city', true]) --> 按城市排序(降序,不区分大小写)

objSort(homes, 'city', true) --> 按城市然后价格排序(升序,区分大小写)

objSort(homes, 'city', 'price') --> 按城市然后价格排序(均升序,不区分大小写)

objSort(homes, 'city', ['price', true]) --> 按城市(升序)然后价格(降序)排序,不区分大小写)

不用多说,函数如下:

function objSort() 
    var args = arguments,
        array = args[0],
        case_sensitive, keys_length, key, desc, a, b, i;

    if (typeof arguments[arguments.length - 1] === 'boolean') 
        case_sensitive = arguments[arguments.length - 1];
        keys_length = arguments.length - 1;
     else 
        case_sensitive = false;
        keys_length = arguments.length;
    

    return array.sort(function (obj1, obj2) 
        for (i = 1; i < keys_length; i++) 
            key = args[i];
            if (typeof key !== 'string') 
                desc = key[1];
                key = key[0];
                a = obj1[args[i][0]];
                b = obj2[args[i][0]];
             else 
                desc = false;
                a = obj1[args[i]];
                b = obj2[args[i]];
            

            if (case_sensitive === false && typeof a === 'string') 
                a = a.toLowerCase();
                b = b.toLowerCase();
            

            if (! desc) 
                if (a < b) return -1;
                if (a > b) return 1;
             else 
                if (a > b) return -1;
                if (a < b) return 1;
            
        
        return 0;
    );
 //end of objSort() function

这里有一些示例数据:

var homes = [
    "h_id": "3",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "price": 162500
, 
    "h_id": "4",
    "city": "Bevery Hills",
    "state": "CA",
    "zip": "90210",
    "price": 1000000
, 
    "h_id": "5",
    "city": "new york",
    "state": "NY",
    "zip": "00010",
    "price": 1000000
, 
    "h_id": "6",
    "city": "Dallas",
    "state": "TX",
    "zip": "85000",
    "price": 300000
, 
    "h_id": "7",
    "city": "New York",
    "state": "NY",
    "zip": "00020",
    "price": 345000
];

【讨论】:

【参考方案7】:

这是一个完整的作弊,但我认为它为这个问题增加了价值,因为它基本上是一个罐装库函数,您可以开箱即用。

如果您的代码可以访问lodash 或类似underscore 的lodash 兼容库,那么您可以使用_.sortBy 方法。下面的sn-p是直接从lodash documentation复制过来的。

示例中的注释结果看起来像是返回数组数组,但这只是显示顺序,而不是对象数组的实际结果。

var users = [
   'user': 'fred',   'age': 48 ,
   'user': 'barney', 'age': 36 ,
   'user': 'fred',   'age': 40 ,
   'user': 'barney', 'age': 34 
];

_.sortBy(users, [function(o)  return o.user; ]);
 // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]

【讨论】:

【参考方案8】:

这是一种按多个字段排序的可扩展方式。

homes.sort(function(left, right) 
    var city_order = left.city.localeCompare(right.city);
    var price_order = parseInt(left.price) - parseInt(right.price);
    return city_order || -price_order;
);

注意事项

传递给array sort 的函数应返回负数、零、正数以表示小于、等于、大于。 a.localeCompare(b) 是 universally supported 对于字符串,如果 a&lt;b,a==b,a&gt;b 则返回 -1,0,1。 减法适用于数值字段,因为如果a&lt;b,a==b,a&gt;ba - b 给出 -,0,+。 最后一行的|| 赋予city 优先于price。 否定任何字段中的反转顺序,如-price_order 向 or 链添加新字段:return city_order || -price_order || date_order; Date compare 有减法,因为 date math 自 1970 年以来转换为毫秒。var date_order = new Date(left.date) - new Date(right.date); Boolean 与减法比较,即guaranteed 将真假转为 1 和 0(因此减法产生 -1 或 0 或 1)。 var goodness_order = Boolean(left.is_good) - Boolean(right.is_good)这很不寻常,我建议使用布尔构造函数引起注意,即使它们已经是布尔值。

【讨论】:

这真是太好了。你如何比较布尔值...错误地将布尔比较转换为 -1、0、1?【参考方案9】:

使用 MULTIPLE 键的动态方式:

从排序的每个 col/key 中过滤唯一值 排序或倒序 根据 indexOf(value) 键值为每个对象添加权重宽度 zeropad 使用计算权重排序

Object.defineProperty(Array.prototype, 'orderBy', 
value: function(sorts)  
    sorts.map(sort =>             
        sort.uniques = Array.from(
            new Set(this.map(obj => obj[sort.key]))
        );
        
        sort.uniques = sort.uniques.sort((a, b) => 
            if (typeof a == 'string') 
                return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
            
            else if (typeof a == 'number') 
                return sort.inverse ? b - a : a - b;
            
            else if (typeof a == 'boolean') 
                let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
                return x;
            
            return 0;
        );
    );

    const weightOfObject = (obj) => 
        let weight = "";
        sorts.map(sort => 
            let zeropad = `$sort.uniques.length`.length;
            weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
        );
        //obj.weight = weight; // if you need to see weights
        return weight;
    

    this.sort((a, b) => 
        return weightOfObject(a).localeCompare( weightOfObject(b) );
    );
    
    return this;

);

用途:

// works with string, number and boolean
let sortered = your_array.orderBy([
    key: "type", inverse: false, 
    key: "title", inverse: false,
    key: "spot", inverse: false,
    key: "internal", inverse: true
]);

【讨论】:

看起来很有希望的解决方案,但不确定如何使用它?我正在用打字稿做一个角离子项目,在组件文件中如何定义这个/或在项目中添加这个? 嗨@Hemang,将代码复制到像array_object_multiple_order.js这样的文件中,将文件导入您的项目,现在您可以从您的对象数组中调用.orderBy【参考方案10】:

这是另一个可能更接近您对语法的想法

function sortObjects(objArray, properties /*, primers*/) 
    var primers = arguments[2] || ; // primers are optional

    properties = properties.map(function(prop) 
        if( !(prop instanceof Array) ) 
            prop = [prop, 'asc']
        
        if( prop[1].toLowerCase() == 'desc' ) 
            prop[1] = -1;
         else 
            prop[1] = 1;
        
        return prop;
    );

    function valueCmp(x, y) 
        return x > y ? 1 : x < y ? -1 : 0; 
    

    function arrayCmp(a, b) 
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) 
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) 
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        );
        return arr1 < arr2 ? -1 : 1;
    

    objArray.sort(function(a, b) 
        return arrayCmp(a, b);
    );


// just for fun use this to reverse the city name when sorting
function demoPrimer(str) 
    return str.split('').reverse().join('');


// Example
sortObjects(homes, ['city', ['price', 'desc']], city: demoPrimer);

演示:http://jsfiddle.net/Nq4dk/2/


编辑:只是为了好玩,here's a variation 只需要一个类似 sql 的字符串,所以你可以这样做 sortObjects(homes, "city, price desc")

function sortObjects(objArray, properties /*, primers*/) 
    var primers = arguments[2] || ;

    properties = properties.split(/\s*,\s*/).map(function(prop) 
        prop = prop.match(/^([^\s]+)(\s*desc)?/i);
        if( prop[2] && prop[2].toLowerCase() === 'desc' ) 
            return [prop[1] , -1];
         else 
            return [prop[1] , 1];
        
    );

    function valueCmp(x, y) 
        return x > y ? 1 : x < y ? -1 : 0; 
    

    function arrayCmp(a, b) 
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) 
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) 
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        );
        return arr1 < arr2 ? -1 : 1;
    

    objArray.sort(function(a, b) 
        return arrayCmp(a, b);
    );

【讨论】:

这个解决方案很干净,但由于数组比较,性能不佳。您可以简单地查看属性,跟踪比较的值,它不为零,返回。这要快得多。【参考方案11】:

这是一个通用的多维排序,允许在每个级别上进行反转和/或映射。

用打字稿写的。对于 Javascript,请查看 JSFiddle

代码

type itemMap = (n: any) => any;

interface SortConfig<T> 
  key: keyof T;
  reverse?: boolean;
  map?: itemMap;


export function byObjectValues<T extends object>(keys: ((keyof T) | SortConfig<T>)[]): (a: T, b: T) => 0 | 1 | -1 
  return function(a: T, b: T) 
    const firstKey: keyof T | SortConfig<T> = keys[0];
    const isSimple = typeof firstKey === 'string';
    const key: keyof T = isSimple ? (firstKey as keyof T) : (firstKey as SortConfig<T>).key;
    const reverse: boolean = isSimple ? false : !!(firstKey as SortConfig<T>).reverse;
    const map: itemMap | null = isSimple ? null : (firstKey as SortConfig<T>).map || null;

    const valA = map ? map(a[key]) : a[key];
    const valB = map ? map(b[key]) : b[key];
    if (valA === valB) 
      if (keys.length === 1) 
        return 0;
      
      return byObjectValues<T>(keys.slice(1))(a, b);
    
    if (reverse) 
      return valA > valB ? -1 : 1;
    
    return valA > valB ? 1 : -1;
  ;

使用示例

按姓氏,然后是名字对人员数组进行排序:

interface Person 
  firstName: string;
  lastName: string;


people.sort(byObjectValues<Person>(['lastName','firstName']));

按语言代码的名称排序,而不是语言代码(参见map),然后按降序版本(参见reverse)。

interface Language 
  code: string;
  version: number;


// languageCodeToName(code) is defined elsewhere in code

languageCodes.sort(byObjectValues<Language>([
  
    key: 'code',
    map(code:string) => languageCodeToName(code),
  ,
  
    key: 'version',
    reverse: true,
  
]));

【讨论】:

【参考方案12】:

更简单的一个:

var someArray = [...];

function generateSortFn(props) 
    return function (a, b) 
        for (var i = 0; i < props.length; i++) 
            var prop = props[i];
            var name = prop.name;
            var reverse = prop.reverse;
            if (a[name] < b[name])
                return reverse ? 1 : -1;
            if (a[name] > b[name])
                return reverse ? -1 : 1;
        
        return 0;
    ;
;

someArray.sort(generateSortFn([name: 'prop1', reverse: true, name: 'prop2']));

【讨论】:

这是列出的最简洁实用的解决方案之一?【参考方案13】:

我喜欢 SnowBurnt 的方法,但它需要调整以测试城市的等效性,而不是差异。

homes.sort(
   function(a,b)
      if (a.city==b.city)
         return (b.price-a.price);
       else 
         return (a.city-b.city);
      
   );

【讨论】:

【参考方案14】:

这是我基于Schwartzian transform idiom 的解决方案,希望对您有用。

function sortByAttribute(array, ...attrs) 
  // generate an array of predicate-objects contains
  // property getter, and descending indicator
  let predicates = attrs.map(pred => 
    let descending = pred.charAt(0) === '-' ? -1 : 1;
    pred = pred.replace(/^-/, '');
    return 
      getter: o => o[pred],
      descend: descending
    ;
  );
  // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
  return array.map(item => 
    return 
      src: item,
      compareValues: predicates.map(predicate => predicate.getter(item))
    ;
  )
  .sort((o1, o2) => 
    let i = -1, result = 0;
    while (++i < predicates.length) 
      if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
      if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
      if (result *= predicates[i].descend) break;
    
    return result;
  )
  .map(item => item.src);

这是一个如何使用它的示例:

let games = [
   name: 'Pako',              rating: 4.21 ,
   name: 'Hill Climb Racing', rating: 3.88 ,
   name: 'Angry Birds Space', rating: 3.88 ,
   name: 'Badland',           rating: 4.33 
];

// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));

【讨论】:

我在这个(和其他页面)上尝试了一些东西。 a8m 的这个解决方案仅适用于我的情况:gist.github.com/cemerson/f1f1434286c1262b403f3d85c96688e0【参考方案15】:

另一种方式

var homes = [
    "h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500",
    "h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250",
    "h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699",
    "h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"
    ];
function sortBy(ar) 
  return ar.sort((a, b) => a.city === b.city ?
      b.price.toString().localeCompare(a.price) :
      a.city.toString().localeCompare(b.city));

console.log(sortBy(homes));

【讨论】:

【参考方案16】:

只是另一种选择。考虑使用以下实用函数:

/** Performs comparing of two items by specified properties
 * @param  Array props for sorting ['name'], ['value', 'city'], ['-date']
 * to set descending order on object property just add '-' at the begining of property
 */
export const compareBy = (...props) => (a, b) => 
  for (let i = 0; i < props.length; i++) 
    const ascValue = props[i].startsWith('-') ? -1 : 1;
    const prop = props[i].startsWith('-') ? props[i].substr(1) : props[i];
    if (a[prop] !== b[prop]) 
      return a[prop] > b[prop] ? ascValue : -ascValue;
    
  
  return 0;
;

使用示例(在您的情况下):

homes.sort(compareBy('city', '-price'));

需要注意的是,这个函数可以更通用化,以便能够使用嵌套属性,如 'address.city' 或 'style.size.width' 等。

【讨论】:

完美数据的好解决方案,但是当缺少键时,它将无法按两个顺序进行排序。有什么想法吗? 解决这个问题是我的解决方案 - js.do/hemangshah-in/569879【参考方案17】:

这是@Snowburnt 解决方案的通用版本:

var sortarray = [field:'city', direction:'asc', field:'price', direction:'desc'];
array.sort(function(a,b)
    for(var i=0; i<sortarray.length; i++)
        retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0;
        if (sortarray[i].direction == "desc") 
            retval = retval * -1;
        
        if (retval !== 0) 
            return retval;
        
    



)

这是基于我正在使用的排序例程。我没有测试这个特定的代码,所以它可能有错误,但你明白了。这个想法是根据指示差异的第一个字段进行排序,然后停止并转到下一条记录。因此,如果您按三个字段排序,并且比较中的第一个字段足以确定要排序的两条记录的排序顺序,则返回该排序结果并转到下一条记录。

我在 5000 条记录上对其进行了测试(实际上使用了更复杂的排序逻辑),并且它眨眼间就完成了。如果您实际上要向客户端加载超过 1000 条记录,您可能应该使用服务器端排序和过滤。

这段代码不区分大小写,但我让读者来处理这个琐碎的修改。

【讨论】:

【参考方案18】:
function sort(data, orderBy) 
        orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
        return data.sort((a, b) => 
            for (let i = 0, size = orderBy.length; i < size; i++) 
                const key = Object.keys(orderBy[i])[0],
                    o = orderBy[i][key],
                    valueA = a[key],
                    valueB = b[key];
                if (!(valueA || valueB)) 
                    console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
                    return [];
                
                if (+valueA === +valueA) 
                    return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
                 else 
                    if (valueA.localeCompare(valueB) > 0) 
                        return o.toLowerCase() === 'desc' ? -1 : 1;
                     else if (valueA.localeCompare(valueB) < 0) 
                        return o.toLowerCase() === 'desc' ? 1 : -1;
                    
                
            
        );
    

使用:

sort(homes, [city : 'asc', price: 'desc'])

var homes = [
    "h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500",
    "h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250",
    "h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699",
    "h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"
    ];
function sort(data, orderBy) 
            orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
            return data.sort((a, b) => 
                for (let i = 0, size = orderBy.length; i < size; i++) 
                    const key = Object.keys(orderBy[i])[0],
                        o = orderBy[i][key],
                        valueA = a[key],
                        valueB = b[key];
                    if (!(valueA || valueB)) 
                        console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
                        return [];
                    
                    if (+valueA === +valueA) 
                        return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
                     else 
                        if (valueA.localeCompare(valueB) > 0) 
                            return o.toLowerCase() === 'desc' ? -1 : 1;
                         else if (valueA.localeCompare(valueB) < 0) 
                            return o.toLowerCase() === 'desc' ? 1 : -1;
                        
                    
                
            );
        
console.log(sort(homes, [city : 'asc', price: 'desc']));

【讨论】:

【参考方案19】:

只需按照您的排序标准列表进行操作

即使您有 36 个排序标准要封装,此代码也将始终保持可读性和可理解性

Nina 在这里提出的解决方案当然非常优雅,但这意味着知道在布尔逻辑中,零值对应于 false 值,并且布尔测试可以在 JavaScript 中返回非真/假(这里是数值),这对于初学者来说总是令人困惑。

还要考虑谁需要维护您的代码。也许会是你:想象你自己花了几天的时间来搜索另一个人的代码并且遇到了一个有害的错误......并且你已经厌倦了阅读这数千行充满提示的行

const homes = 
  [  h_id: '3', city: 'Dallas',       state: 'TX', zip: '75201', price: '162500'  
  ,  h_id: '4', city: 'Bevery Hills', state: 'CA', zip: '90210', price: '319250'  
  ,  h_id: '6', city: 'Dallas',       state: 'TX', zip: '75000', price: '556699'  
  ,  h_id: '5', city: 'New York',     state: 'NY', zip: '00010', price: '962500'  
  ]
  
const fSort = (a,b) =>
  
  let Dx = a.city.localeCompare(b.city)              // 1st criteria
  if (Dx===0) Dx = Number(b.price) - Number(a.price) // 2nd

  // if (Dx===0) Dx = ... // 3rd
  // if (Dx===0) Dx = ... // 4th....
  return Dx
  

console.log( homes.sort(fSort))

【讨论】:

【参考方案20】:

添加几个辅助函数可以让您通用而简单地解决此类问题。 sortByKey 接受一个数组和一个函数,该函数应该返回一个项目列表,用于比较每个数组条目。

这利用了 javascript 对简单值数组与[0] &lt; [0, 0] &lt; [0, 1] &lt; [1, 0] 进行智能比较这一事实。


// Two helpers:
function cmp(a, b) 
    if (a > b) 
        return 1
     else if (a < b) 
        return -1
     else 
        return 0
    


function sortByKey(arr, key) 
    arr.sort((a, b) => cmp(key(a), key(b)))


// A demonstration:
let arr = [a:1, b:2, b:3, a:0, a:1, b:1, a:2, b:2, a:2, b:1]
sortByKey(arr, item => [item.a, item.b])

console.log(JSON.stringify(arr))
// '["b":3,"a":0,"a":1,"b":1,"a":1,"b":2,"a":2,"b":1,"a":2,"b":2]'

sortByKey(arr, item => [item.b, item.a])
console.log(JSON.stringify(arr))
//'["a":1,"b":1,"a":2,"b":1,"a":1,"b":2,"a":2,"b":2,"b":3,"a":0]'

我从 Python 的 list.sort 函数中巧妙地借鉴了这个想法。

【讨论】:

这仅适用于单个数字。 [1, 0] &lt; [10, 0] &lt; [2, 0]【参考方案21】:
function sortMultiFields(prop)
    return function(a,b)
        for(i=0;i<prop.length;i++)
        
            var reg = /^\d+$/;
            var x=1;
            var field1=prop[i];
            if(prop[i].indexOf("-")==0)
            
                field1=prop[i].substr(1,prop[i].length);
                x=-x;
            

            if(reg.test(a[field1]))
            
                a[field1]=parseFloat(a[field1]);
                b[field1]=parseFloat(b[field1]);
            
            if( a[field1] > b[field1])
                return x;
            else if(a[field1] < b[field1])
                return -x;
        
    

如何使用(如果要对特定字段进行降序排序,请在字段前加上-(减号))

homes.sort(sortMultiFields(["city","-price"]));

使用上述函数,您可以对具有多个字段的任何 json 数组进行排序。 完全不需要改变函数体

【讨论】:

【参考方案22】:

改编@chriskelly 的回答。


大多数答案忽略了如果价值在万以下或超过一百万,价格将无法正确排序。 JS 的原因是按字母顺序排序。 Why can't JavaScript sort "5, 10, 1" 和 How to sort an array of integers correctly 在这里得到了很好的回答。

如果我们排序的字段或节点是一个数字,最终我们必须进行一些评估。我并不是说在这种情况下使用parseInt() 是正确答案,排序后的结果更重要。

var homes = [
  "h_id": "2",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62500"
, 
  "h_id": "1",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62510"
, 
  "h_id": "3",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "162500"
, 
  "h_id": "4",
  "city": "Bevery Hills",
  "state": "CA",
  "zip": "90210",
  "price": "319250"
, 
  "h_id": "6",
  "city": "Dallas",
  "state": "TX",
  "zip": "75000",
  "price": "556699"
, 
  "h_id": "5",
  "city": "New York",
  "state": "NY",
  "zip": "00010",
  "price": "962500"
];

homes.sort(fieldSorter(['price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) 
  return function(a, b) 
    return fields
      .map(function(o) 
        var dir = 1;
        if (o[0] === '-') 
          dir = -1;
          o = o.substring(1);
        
        if (!parseInt(a[o]) && !parseInt(b[o])) 
          if (a[o] > b[o]) return dir;
          if (a[o] < b[o]) return -(dir);
          return 0;
         else 
          return dir > 0 ? a[o] - b[o] : b[o] - a[o];
        
      )
      .reduce(function firstNonZeroValue(p, n) 
        return p ? p : n;
      , 0);
  ;

document.getElementById("output").innerhtml = '<pre>' + JSON.stringify(homes, null, '\t') + '</pre>';
<div id="output">

</div>

A fiddle to test with

【讨论】:

问题在于您尝试排序的数据。示例中的price 是字符串格式。如果您希望它与我的示例一起正常工作,请先使用 map 将您想要的字段转换为数字格式。即const correctedHomes = homes.map(h =&gt; (...h, price: +h.price))【参考方案23】:

哇,这里有一些复杂的解决方案。如此复杂,我决定想出一些更简单但也很强大的东西。在这里;

function sortByPriority(data, priorities) 
  if (priorities.length == 0) 
    return data;
  

  const nextPriority = priorities[0];
  const remainingPriorities = priorities.slice(1);

  const matched = data.filter(item => item.hasOwnProperty(nextPriority));
  const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

  return sortByPriority(matched, remainingPriorities)
    .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
    .concat(sortByPriority(remainingData, remainingPriorities));

这里有一个你如何使用它的例子。

const data = [
   id: 1,                         mediumPriority: 'bbb', lowestPriority: 'ggg' ,
   id: 2, highestPriority: 'bbb', mediumPriority: 'ccc', lowestPriority: 'ggg' ,
   id: 3,                         mediumPriority: 'aaa', lowestPriority: 'ggg' ,
];

const priorities = [
  'highestPriority',
  'mediumPriority',
  'lowestPriority'
];


const sorted = sortByPriority(data, priorities);

这将首先按属性的优先级排序,然后按属性的值。

【讨论】:

【参考方案24】:

我认为这可能是最简单的方法。

https://coderwall.com/p/ebqhca/javascript-sort-by-two-fields

这真的很简单,我尝试了 3 个不同的键值对,效果很好。

这里是一个简单的例子,查看链接了解更多详情

testSort(data) 
    return data.sort(
        a['nameOne'] > b['nameOne'] ? 1
        : b['nameOne'] > a['nameOne'] ? -1 : 0 ||
        a['date'] > b['date'] ||
        a['number'] - b['number']
    );

【讨论】:

【参考方案25】:

这是我的参考,例如:

function msort(arr, ...compFns) 
  let fn = compFns[0];
  arr = [].concat(arr);
  let arr1 = [];
  while (arr.length > 0) 
    let arr2 = arr.splice(0, 1);
    for (let i = arr.length; i > 0;) 
      if (fn(arr2[0], arr[--i]) === 0) 
        arr2 = arr2.concat(arr.splice(i, 1));
      
    
    arr1.push(arr2);
  

  arr1.sort(function (a, b) 
    return fn(a[0], b[0]);
  );

  compFns = compFns.slice(1);
  let res = [];
  arr1.map(a1 => 
    if (compFns.length > 0) a1 = msort(a1, ...compFns);
    a1.map(a2 => res.push(a2));
  );
  return res;


let tstArr = [ id: 1, sex: 'o' ,  id: 2, sex: 'm' ,  id: 3, sex: 'm' ,  id: 4, sex: 'f' ,  id: 5, sex: 'm' ,  id: 6, sex: 'o' ,  id: 7, sex: 'f' ];

function tstFn1(a, b) 
  if (a.sex > b.sex) return 1;
  else if (a.sex < b.sex) return -1;
  return 0;


function tstFn2(a, b) 
  if (a.id > b.id) return -1;
  else if (a.id < b.id) return 1;
  return 0;


console.log(JSON.stringify(msort(tstArr, tstFn1, tstFn2)));
//output:
//["id":7,"sex":"f","id":4,"sex":"f","id":5,"sex":"m","id":3,"sex":"m","id":2,"sex":"m","id":6,"sex":"o","id":1,"sex":"o"]

【讨论】:

【参考方案26】:

我一直在寻找类似的东西,结果是这样的:

首先我们有一个或多个排序函数,总是返回 0、1 或 -1:

const sortByTitle = (a, b): number => 
  a.title === b.title ? 0 : a.title > b.title ? 1 : -1;

您可以为要排序的每个其他属性创建更多函数。

然后我有一个函数将这些排序功能合二为一:

const createSorter = (...sorters) => (a, b) =>
  sorters.reduce(
    (d, fn) => (d === 0 ? fn(a, b) : d),
    0
  );

这可用于以可读的方式组合上述排序功能:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

当排序函数返回 0 时,将调用下一个排序函数进行进一步排序。

【讨论】:

【参考方案27】:

这是一种递归算法,可按多个字段排序,同时有机会在比较之前格式化值。

var data = [

    "id": 1,
    "ship": null,
    "product": "Orange",
    "quantity": 7,
    "price": 92.08,
    "discount": 0
,

    "id": 2,
    "ship": "2017-06-14T23:00:00.000Z".toDate(),
    "product": "Apple",
    "quantity": 22,
    "price": 184.16,
    "discount": 0
,
...
]
var sorts = ["product", "quantity", "ship"]

// comp_val formats values and protects against comparing nulls/undefines
// type() just returns the variable constructor
// String.lower just converts the string to lowercase.
// String.toDate custom fn to convert strings to Date
function comp_val(value)
    if (value==null || value==undefined) return null
    var cls = type(value)
    switch (cls)
        case String:
            return value.lower()
    
    return value


function compare(a, b, i)
    i = i || 0
    var prop = sorts[i]
    var va = comp_val(a[prop])
    var vb = comp_val(b[prop])

    // handle what to do when both or any values are null
    if (va == null || vb == null) return true

    if ((i < sorts.length-1) && (va == vb)) 
        return compare(a, b, i+1)
     
    return va > vb


var d = data.sort(compare);
console.log(d);

如果 a 和 b 相等,它将尝试下一个字段,直到没有可用为止。

【讨论】:

【参考方案28】:
// custom sorting by city
const sortArray = ['Dallas', 'New York', 'Beverly Hills'];

const sortData = (sortBy) =>
  data
    .sort((a, b) => 
      const aIndex = sortBy.indexOf(a.city);
      const bIndex = sortBy.indexOf(b.city);

      if (aIndex < bIndex) 
        return -1;
      

      if (aIndex === bIndex) 
        // price descending
        return b.price- a.price;
      

      return 1;
    );

sortData(sortArray);

【讨论】:

【参考方案29】:

你可以使用lodash orderBy函数lodash

它需要两个参数的字段数组和方向数组('asc','desc')

  var homes = [
    "h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500",
    "h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250",
    "h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699",
    "h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"
    ];

var sorted =. data._.orderBy(data, ['city', 'price'], ['asc','desc'])

【讨论】:

【参考方案30】:

这里 'AffiliateDueDate' 和 'Title' 是列,都是按升序排列的。

array.sort(function(a, b) 

               if (a.AffiliateDueDate > b.AffiliateDueDate ) return 1;
               else if (a.AffiliateDueDate < b.AffiliateDueDate ) return -1;
               else if (a.Title > b.Title ) return 1;
               else if (a.Title < b.Title ) return -1;
               else return 0;
             )

【讨论】:

以上是关于如何按多个字段对对象数组进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

对JavaScript对象数组按指定属性和排序方向进行排序

Java按整数字段对对象数组进行排序,如果它们相同,则按另一个整数值排序

按日期对具有日期字段的对象数组进行排序

首先按字母顺序对对象数组进行排序,然后按数字排序

数组对象根据 某个字段进行排序

JavaScript实现对象数组按不同字段排序