【问题标题】:Sort array of objects by string property valueSort array of objects by string property value
【发布时间】:2022-11-14 16:56:30
【问题描述】:

I have an array of JavaScript objects:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

How can I sort them by the value of last_nom in JavaScript?

I know about sort(a,b), but that only seems to work on strings and numbers. Do I need to add a toString() method to my objects?

【问题讨论】:

  • This script allows you to do just that unless you want to write your own comparison function or sorter: thomasfrank.se/sorting_things.html
  • the quickest way is to use the isomorphic sort-array module which works natively in both browser and node, supporting any type of input, computed fields and custom sort orders.
  • function compare( a, b ) { if ( a.last_nom < b.last_nom ){ return -1; } if ( a.last_nom > b.last_nom ){ return 1; } return 0; } objs.sort( compare );
  • objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0))
  • @RobertTalada it's my answer stackoverflow.com/a/67021585/7012450

标签: javascript arrays sorting comparison


【解决方案1】:

It's easy enough to write your own comparison function:

function compare( a, b ) {
  if ( a.last_nom < b.last_nom ){
    return -1;
  }
  if ( a.last_nom > b.last_nom ){
    return 1;
  }
  return 0;
}

objs.sort( compare );

Or inline (c/o Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0))

Or simplified for numeric (c/o Andre Figueiredo):

objs.sort((a,b) => a.last_nom - b.last_nom); // b - a for reverse sort

【讨论】:

  • Or inline: objs.sort(function(a,b) {return (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0);} );
  • return a.last_nom.localeCompare(b.last_nom) will work, too.
  • for those looking for a sort where the field is numeric, the compare function body: return a.value - b.value; (ASC)
  • You can convert a string to a number by using charCodeAt, then use the numeric inline above for a more concise one liner: objs.sort((a,b) =&gt; a.last_nom.charCodeAt(0) - b.last_nom.charCodeAt(0));. This avoids the ugly nested ternary.
【解决方案2】:

You can also create a dynamic sort function that sorts objects by their value that you pass:

function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        /* next line works with strings and numbers, 
         * and you may want to customize it to your needs
         */
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

So you can have an array of objects like this:

var People = [
    {Name: "Name", Surname: "Surname"},
    {Name:"AAA", Surname:"ZZZ"},
    {Name: "Name", Surname: "AAA"}
];

...and it will work when you do:

People.sort(dynamicSort("Name"));
People.sort(dynamicSort("Surname"));
People.sort(dynamicSort("-Surname"));

Actually this already answers the question. Below part is written because many people contacted me, complaining that it doesn't work with multiple parameters.

Multiple Parameters

You can use the function below to generate sort functions with multiple sort parameters.

function dynamicSortMultiple() {
    /*
     * save the arguments object as it will be overwritten
     * note that arguments object is an array-like object
     * consisting of the names of the properties to sort by
     */
    var props = arguments;
    return function (obj1, obj2) {
        var i = 0, result = 0, numberOfProperties = props.length;
        /* try getting a different result from 0 (equal)
         * as long as we have extra properties to compare
         */
        while(result === 0 && i < numberOfProperties) {
            result = dynamicSort(props[i])(obj1, obj2);
            i++;
        }
        return result;
    }
}

Which would enable you to do something like this:

People.sort(dynamicSortMultiple("Name", "-Surname"));

Subclassing Array

For the lucky among us who can use ES6, which allows extending the native objects:

class MyArray extends Array {
    sortBy(...args) {
        return this.sort(dynamicSortMultiple(...args));
    }
}

That would enable this:

MyArray.from(People).sortBy("Name", "-Surname");

【讨论】:

  • Nice. There is now a Typescript version of this answer: stackoverflow.com/a/68279093/8910547. Stay (type) safe! ?
  • You shouldn't ever extend Array.
  • @zero_cool Array isn't being extended here (prototype stays the same), it's extended from. You indeed shouldn't change the prototype of native objects, but as I said, that's not what happens here.
  • not testing for null
  • @serge any comparison of strings and nulls will result to false, putting null values at the end. if you change the a[property] < b[property] to a[property].localeCompare(b[property]), you can do a[property]?.localeCompare(b[property]) ?? 1 (take b as first if a has empty in property, and localeCompare will return -1 anyway if b has null at property -- illogical when both null though, so check for that as well maybe)
【解决方案3】:

In ES6/ES2015 or later you can do this way:

objs.sort((a, b) => a.last_nom.localeCompare(b.last_nom));

Prior to ES6/ES2015

objs.sort(function(a, b) {
    return a.last_nom.localeCompare(b.last_nom)
});

【讨论】:

  • this has been available since JS 1.1, the fat arrow piece to this is the ES6/2015 piece. But still very useful, and best answer in my opinion
  • @PratikKelwalkar: if you need reverse it just switch a and b comparison: objs.sort((a, b) => b.last_nom.localeCompare(a.last_nom));
  • If the values are numeric, you don't need localeCompare. You can use the standard &gt; operator - like mentioned in the answer by @muasif80 - stackoverflow.com/a/67992215/6908282
  • The simplier solution for this problem at the present time.
  • IMHO this should be the accepted answer
【解决方案4】:

underscore.js

use underscore, its small and awesome...

sortBy_.sortBy(list, iterator, [context]) Returns a sorted copy of list, ranked in ascending order by the results of running each value through iterator. Iterator may also be the string name of the property to sort by (eg. length).

var objs = [ 
  { first_nom: 'Lazslo',last_nom: 'Jamf' },
  { first_nom: 'Pig', last_nom: 'Bodine'  },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

var sortedObjs = _.sortBy( objs, 'first_nom' );

【讨论】:

  • David, could you edit the answer to say, var sortedObjs = _.sortBy( objs, 'first_nom' );. objs willnotbe sorted itself as a result of this. The function willreturna sorted array. That would make it more explicit.
  • To reverse sort: var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse();
  • you need to load the javascript libary "underscore": &lt;script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"&gt; &lt;/script&gt;
  • Also available in Lodash for the ones who prefer that one
  • In lodash this would be the same: var sortedObjs = _.sortBy( objs, 'first_nom' ); or if you want it in a different order: var sortedObjs = _.orderBy( objs, ['first_nom'],['dsc'] );
【解决方案5】:

Case sensitive

arr.sort((a, b) => a.name > b.name ? 1 : -1);

Case Insensitive

arr.sort((a, b) => a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1);

Useful Note

If no change in order (in case of the same strings) then the condition &gt; will fail and -1 will be returned. But if strings are same then returning 1 or -1 will result in correct output

The other option could be to use &gt;= operator instead of &gt;


var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];


// Define a couple of sorting callback functions, one with hardcoded sort key and the other with an argument sort key
const sorter1 = (a, b) => a.last_nom.toLowerCase() > b.last_nom.toLowerCase() ? 1 : -1;
const sorter2 = (sortBy) => (a, b) => a[sortBy].toLowerCase() > b[sortBy].toLowerCase() ? 1 : -1;

objs.sort(sorter1);
console.log("Using sorter1 - Hardcoded sort property last_name", objs);

objs.sort(sorter2('first_nom'));
console.log("Using sorter2 - passed param sortBy='first_nom'", objs);

objs.sort(sorter2('last_nom'));
console.log("Using sorter2 - passed param sortBy='last_nom'", objs);

【讨论】:

  • The case sensitive approach is a good shorthand - especially if the values are numeric or date.
  • TIP: if you'd like to reverse the order, you can simply swap -1 and 1 for eg: from 1 : -1 to -1 : 1
  • What about changing (a, b) to (b, a) :)
  • Yes, that works too. I just find swapping 1 & -1 more straight forward and logical.
  • @Gangula: It's also more easy to pickup on the reverse order of those, than of parameter names, but I suppose that comes with more experience reading code anyway (in case of maintenance). What I like best is the omission of another test to leave the items as be (return 0) in case they're the same... if the items are the same, I don't mind the swapping.
【解决方案6】:

If you have duplicate last names you might sort those by first name-

obj.sort(function(a,b){
  if(a.last_nom< b.last_nom) return -1;
  if(a.last_nom >b.last_nom) return 1;
  if(a.first_nom< b.first_nom) return -1;
  if(a.first_nom >b.first_nom) return 1;
  return 0;
});

【讨论】:

  • @BadFeelingAboutThis what does returning either -1 or 1 mean? I understand that -1 literally means that A is less than B just by the syntax, but why use a 1 or -1? I see everyone is using those numbers as return values, but why? Thanks.
  • @Chris22 a negative number returned means that b should come after a in the array. If a positive number is returned, it means a should come after b. If 0 is returned, it means they are considered equal. You can always read the documentation: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • @BadFeelingAboutThis thanks, for the explanation and the link. Believe it or not, I googled various sn-ps of code using the 1, 0, -1 before I asked this here. I just wasn't finding the info I needed.
【解决方案7】:

As of 2018 there is a much shorter and elegant solution. Just use. Array.prototype.sort().

Example:

var items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 13 },
  { name: 'Zeros', value: 37 }
];

// sort by value
items.sort(function (a, b) {
  return a.value - b.value;
});

【讨论】:

  • In the question strings were used for comparison as opposed to numbers. Your answer works great for sorting by numbers, but isn't so good for comparisons by string.
  • The a.value - b.value used to compare the object's attributes (numbersin this case) can be adopted for the various times of data. For example, regex can be used to compare each pair of the neighboringstrings.
  • This implementation is quite good if you need to sort it by ID. Yeah , you have suggested to use regex to compare neighbouring string which makes solution more complicated whereas purpose of this simpliefied version will be otherwise if regex is used along with given solution. Simplicity is the best.
【解决方案8】:

Simple and quick solution to this problem using prototype inheritance:

Array.prototype.sortBy = function(p) {
  return this.slice(0).sort(function(a,b) {
    return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
  });
}

Example / Usage

objs = [{age:44,name:'vinay'},{age:24,name:'deepak'},{age:74,name:'suresh'}];

objs.sortBy('age');
// Returns
// [{"age":24,"name":"deepak"},{"age":44,"name":"vinay"},{"age":74,"name":"suresh"}]

objs.sortBy('name');
// Returns
// [{"age":24,"name":"deepak"},{"age":74,"name":"suresh"},{"age":44,"name":"vinay"}]

Update:No longer modifies original array.

【讨论】:

  • It dosn't just return another array. but actually sorts the original one!.
  • If you want to make sure you are using a natural sort with numbers (i.e., 0,1,2,10,11 etc...) use parseInt with the Radix set. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… so: return (parseInt(a[p],10) > parseInt(b[p],10)) ? 1 : (parseInt(a[p],10) < parseInt(b[p],10)) ? -1 : 0;
  • @codehuntr Thanks for correcting it. but i guess instead of making sort function to do this sensitization, it's better if we make a separate function to fix data types. Because sort function can't not tell which property will contain what kind of data. :)
  • i think this will only work on some prop types.. you'd want to add date/string handling etc.. i.e. if type is string use return a.localCompare(b) etc. etc..
【解决方案9】:

Old answer that is not correct:

arr.sort((a, b) => a.name > b.name)

UPDATE

From Beauchamp's comment:

arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))

More readable format:

arr.sort((a, b) => {
  if (a.name < b.name) return -1
  return a.name > b.name ? 1 : 0
})

Without nested ternaries:

arr.sort((a, b) => a.name < b.name ? - 1 : Number(a.name > b.name))

Explanation: Number() will cast true to 1 and false to 0.

【讨论】:

  • It works, but the result is unstable for some reason
  • @AO17 no it won't. You can't subtract strings.
  • This should do it: arr.sort((a, b) =&gt; a.name &lt; b.name ? -1 : (a.name &gt; b.name ? 1 : 0))
  • @Jean-FrançoisBeauchamp, your solution works perfectly fine and much better.
  • Why would arr.sort((a, b) =&gt; a.name &gt; b.name ? 1 : -1 will not work? For Strings i have tested this works great. If you want case insensitive then use a.name.toLowerCase() and b.name.toLowerCase()
【解决方案10】:

Lodash.js (superset of Underscore.js)

It's good not to add a framework for every simple piece of logic, but relying on well tested utility frameworks can speed up development and reduce the amount of bugs.

Lodash produces very clean code and promotes a morefunctional programmingstyle. In one glimpse it becomes clear what the intent of the code is.

OP's issue can simply be solved as:

const sortedObjs = _.sortBy(objs, 'last_nom');

More info? E.g. we have following nested object:

const users = [
  { 'user': {'name':'fred', 'age': 48}},
  { 'user': {'name':'barney', 'age': 36 }},
  { 'user': {'name':'wilma'}},
  { 'user': {'name':'betty', 'age': 32}}
];

We now can use the _.property shorthand user.age to specify the path to the property that should be matched. We will sort the user objects by the nested age property. Yes, it allows for nested property matching!

const sortedObjs = _.sortBy(users, ['user.age']);

Want it reversed? No problem. Use _.reverse.

const sortedObjs = _.reverse(_.sortBy(users, ['user.age']));

Want to combine both using chain?

const { chain } = require('lodash');
const sortedObjs = chain(users).sortBy('user.age').reverse().value();

Or when do you prefer flow over chain

const { flow, reverse, sortBy } = require('lodash/fp');
const sortedObjs = flow([sortBy('user.age'), reverse])(users); 

【讨论】:

    【解决方案11】:

    You can use Easiest Way:Lodash

    (https://lodash.com/docs/4.17.10#orderBy)

    This method is like _.sortBy except that it allows specifying the sort orders of the iteratees to sort by. If orders is unspecified, all values are sorted in ascending order. Otherwise, specify an order of "desc" for descending or "asc" for ascending sort order of corresponding values.

    Arguments

    collection (Array|Object): The collection to iterate over. [iteratees=[_.identity]] (Array[]|Function[]|Object[]|string[]): The iteratees to sort by. [orders] (string[]): The sort orders of iteratees.

    Returns

    (Array): Returns the new sorted array.


    var _ = require('lodash');
    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"}
        ];
        
    _.orderBy(homes, ['city', 'state', 'zip'], ['asc', 'desc', 'asc']);
    

    【讨论】:

      【解决方案12】:

      I haven't seen this particular approach suggested, so here's a terse comparison method I like to use that works for both string and number types:

      const objs = [ 
        { first_nom: 'Lazslo', last_nom: 'Jamf'     },
        { first_nom: 'Pig',    last_nom: 'Bodine'   },
        { first_nom: 'Pirate', last_nom: 'Prentice' }
      ];
      
      const sortBy = fn => {
        const cmp = (a, b) => -(a < b) || +(a > b);
        return (a, b) => cmp(fn(a), fn(b));
      };
      
      const getLastName = o => o.last_nom;
      const sortByLastName = sortBy(getLastName);
      
      objs.sort(sortByLastName);
      console.log(objs.map(getLastName));

      Explanation of sortBy()

      sortBy() accepts a fn that selects a value from an object to use in comparison, and returns a function that can be passed to Array.prototype.sort(). In this example, we're comparing o.last_nom. Whenever we receive two objects such as

      a = { first_nom: 'Lazslo', last_nom: 'Jamf' }
      b = { first_nom: 'Pig', last_nom: 'Bodine' }
      

      we compare them with (a, b) =&gt; cmp(fn(a), fn(b)). Given that

      fn = o => o.last_nom
      

      we can expand the comparison function to (a, b) =&gt; cmp(a.last_nom, b.last_nom). Because of the way logical OR (||) works in JavaScript, cmp(a.last_nom, b.last_nom) is equivalent to

      if (a.last_nom < b.last_nom) return -1;
      if (a.last_nom > b.last_nom) return 1;
      return 0;
      

      Incidentally, this is called the three-way comparison "spaceship" (&lt;=&gt;) operator in other languages.

      Finally, here's the ES5-compatible syntax without using arrow functions:

      var objs = [ 
        { first_nom: 'Lazslo', last_nom: 'Jamf'     },
        { first_nom: 'Pig',    last_nom: 'Bodine'   },
        { first_nom: 'Pirate', last_nom: 'Prentice' }
      ];
      
      function sortBy(fn) {
        function cmp(a, b) { return -(a < b) || +(a > b); }
        return function (a, b) { return cmp(fn(a), fn(b)); };
      }
      
      function getLastName(o) { return o.last_nom; }
      var sortByLastName = sortBy(getLastName);
      
      objs.sort(sortByLastName);
      console.log(objs.map(getLastName));

      【讨论】:

      • I like this approach but I think using the shorthand of -(fa &lt; fb) || +(fa &gt; fb) is a mistake here. That's multiple statements being condensed into one line of code. The alternative, written with an if statement, would be much more readable whilst still being fairly concise. I think it's a mistake to sacrifice readability for prettiness.
      • @MSOACC thanks for your opinion but I respectfully disagree. Other languages implement a three-way comparison operator that performs the same comparison, so just think of it conceptually as fa &lt;=&gt; fb.
      • Hey Patrick, I like your answer but it's would work properly only with English characters (const cmp = (a, b) =&gt; -(a &lt; b) || +(a &gt; b);) Think of ["ä", "a", "c", "b"].sort(cmp) => ["a", "b", "c", "ä"], where ä is pushed to the end. Instead you should probably update the comparison function to: const cmp = (a, b) =&gt; a.localeCompare(b); => ["a", "ä", "b", "c"] Cheers and thanks for the answer ;-)
      • @rjanjic thanks for the feedback. I'm aware that it sorts based on the code point of the character in unicode. However changing it to use localeCompare removes the ability to sort numbers, and is alsosignificantlyslower.
      【解决方案13】:

      Instead of using a custom comparison function, you could also create an object type with custom toString() method (which is invoked by the default comparison function):

      function Person(firstName, lastName) {
          this.firtName = firstName;
          this.lastName = lastName;
      }
      
      Person.prototype.toString = function() {
          return this.lastName + ', ' + this.firstName;
      }
      
      var persons = [ new Person('Lazslo', 'Jamf'), ...]
      persons.sort();
      

      【讨论】:

        【解决方案14】:

        There are many good answers here, but I would like to point out that they can be extended very simply to achieve a lot more complex sorting. The only thing you have to do is to use the OR operator to chain comparision functions like this:

        objs.sort((a,b)=> fn1(a,b) || fn2(a,b) || fn3(a,b) )
        

        Where fn1, fn2, ... are the sort functions which return [-1,0,1]. This results in "sorting by fn1", "sorting by fn2" which is pretty much equal to ORDER BY in SQL.

        This solution is based on the behaviour of || operator which evaluates to the first evaluated expression which can be converted to true.

        The simplest formhas only one inlined function like this:

        // ORDER BY last_nom
        objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) )
        

        Having two steps with last_nom,first_nom sort order would look like this:

        // ORDER_BY last_nom, first_nom
        objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) || 
                          a.first_nom.localeCompare(b.first_nom)  )
        

        A generic comparision functioncould be something like this:

        // ORDER BY <n>
        let cmp = (a,b,n)=>a[n].localeCompare(b[n])
        

        This function could be extended to support numeric fields, case sensitity, arbitary datatypes etc.

        You can them use it with chaining them by sort priority:

        // ORDER_BY last_nom, first_nom
        objs.sort((a,b)=> cmp(a,b, "last_nom") || cmp(a,b, "first_nom") )
        // ORDER_BY last_nom, first_nom DESC
        objs.sort((a,b)=> cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )
        // ORDER_BY last_nom DESC, first_nom DESC
        objs.sort((a,b)=> -cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )
        

        The point here is that pure JavaScript with functional approach can take you a long way without external libraries or complex code. It is also very effective, since no string parsing have to be done

        【讨论】:

          【解决方案15】:

          Try this,

          UPTO ES5
          
          //Ascending Sort
          items.sort(function (a, b) {
             return a.value - b.value;
          });
          
          
          //Descending Sort
          items.sort(function (a, b) {
             return b.value - a.value;
          });
          
          
          IN ES6 & above:
          
          // Ascending sort
          items.sort((a, b) => a.value - b.value);
          
          // Descending Sort
           items.sort((a, b) => b.value - a.value);
          

          【讨论】:

          • best and easy solution
          • Doesn't work for me, tried other solutions that does indeed work but this one doesn't. Attempted to sort by strings.
          【解决方案16】:

          Use JavaScript sort method

          Thesortmethod can be modified to sort anything like an array of numbers, strings and even objects using a compare function.

          A compare function is passed as anoptional argumentto the sort method.

          This compare function accepts 2 arguments generally calledaandb. Based on these 2 arguments you can modify the sort method to work as you want.

          1. If the compare function returns less than 0, then the sort() method sortsaat a lower index thanb. Simply a will come before b.
          2. If the compare function returns equal to 0, then the sort() method leaves the element positions as they are.
          3. If the compare function returns greater than 0, then the sort() method sortsaat greater index thanb. Simplyawill come afterb.

            Use the above concept to apply on your object whereawill be your object property.

            var objs = [
              { first_nom: 'Lazslo', last_nom: 'Jamf' },
              { first_nom: 'Pig', last_nom: 'Bodine' },
              { first_nom: 'Pirate', last_nom: 'Prentice' }
            ];
            function compare(a, b) {
              if (a.last_nom > b.last_nom) return 1;
              if (a.last_nom < b.last_nom) return -1;
              return 0;
            }
            objs.sort(compare);
            console.log(objs)
            // for better look use console.table(objs)

          【讨论】:

            【解决方案17】:

            Example Usage:

            objs.sort(sortBy('last_nom'));
            

            Script:

            /**
             * @description
             * Returns a function which will sort an
             * array of objects by the given key.
             *
             * @param  {String}  key
             * @param  {Boolean} reverse
             * @return {Function}
             */
            const sortBy = (key, reverse) => {
            
              // Move smaller items towards the front
              // or back of the array depending on if
              // we want to sort the array in reverse
              // order or not.
              const moveSmaller = reverse ? 1 : -1;
            
              // Move larger items towards the front
              // or back of the array depending on if
              // we want to sort the array in reverse
              // order or not.
              const moveLarger = reverse ? -1 : 1;
            
              /**
               * @param  {*} a
               * @param  {*} b
               * @return {Number}
               */
              return (a, b) => {
                if (a[key] < b[key]) {
                  return moveSmaller;
                }
                if (a[key] > b[key]) {
                  return moveLarger;
                }
                return 0;
              };
            };
            

            【讨论】:

            • thank you for breaking this down, I am trying to understand why digits 1, 0, -1 are used for sort ordering. Even with your explanation above, which looks very good-- I'm still not quite understanding it. I always think of -1 as when using array length property, i.e.: arr.length = -1 means that the item isn't found. I'm probably mixing things up here, but could you help me understand why digits 1, 0, -1 are used to determine order? Thanks.
            • This isn'tentirelyaccurate but it might help to think of it like this: the function passed to array.sort is called once for each item in the array, as the argument named "a". The return value of each function call is how the index (current position number) of item "a" should be altered compared to the next item "b". The index dictates the order of the array (0, 1, 2 etc) So if "a" is at index 5 and you return -1 then 5 + -1 == 4 (move it nearer front) 5 + 0 == 5 (keep it where it is) etc. It walks the array comparing 2 neighbours each time until it reaches the end, leaving a sorted array.
            • thank you for taking the time to explain this further. So using your explanation and the MDN Array.prototype.sort, I'll tell you what I'm thinking of this: in comparison to a and b, if a is greater than b add 1 to the index of a and place it behind b, if a is less than b, subtract 1 from a and place it in front of b. If a and b are the same, add 0 to a and leave it where it is.
            【解决方案18】:

            Why don't you write short code?

            objs.sort((a, b) => a.last_nom > b.last_nom ? 1 : -1)
            

            【讨论】:

            • what if values are equal? considering there are 3 values you can return - 1, -1, 0
            • @SomeoneSpecial so what? The result will be the same
            • What does 1 || -1 mean ?
            • @KaleemElahi If I understand correctly, he's using it as a bit mask. If a.last_nom > b.last_nom THEN 1 ELSE -1. Effectively moving an item up or down based on the comparison.
            • There is no bit mask, expression a&gt;b &amp;&amp; 1|| -1 is equal to a&gt; b ? 1 : -1, operator &amp;&amp; returns first logical false value, operator || returns first logical true value.
            【解决方案19】:

            I know this question is too old, but I didn't see any implementation similar to mine.
            This version is based on the 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);
            }
            

            Here's an example how to use it:

            let games = [
              { name: 'Mashraki',          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'));
            

            【讨论】:

              【解决方案20】:

              Sorting (more) Complex Arrays of Objects

              Since you probably encounter more complex data structures like this array, I would expand the solution.

              TL;DR

              Are more pluggable version based on@ege-Özcan's very lovely answer.

              Problem

              I encountered the below and couldn't change it. I also did not want to flatten the object temporarily. Nor did I want to use underscore / lodash, mainly for performance reasons and the fun to implement it myself.

              var People = [
                 {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
                 {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
                 {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
              ];
              

              Goal

              The goal is to sort it primarily by People.Name.name and secondarily by People.Name.surname

              Obstacles

              Now, in the base solution uses bracket notation to compute the properties to sort for dynamically. Here, though, we would have to construct the bracket notation dynamically also, since you would expect some like People['Name.name'] would work - which doesn't.

              Simply doing People['Name']['name'], on the other hand, is static and only allows you to go down then-th level.

              Solution

              The main addition here will be to walk down the object tree and determine the value of the last leaf, you have to specify, as well as any intermediary leaf.

              var People = [
                 {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
                 {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
                 {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
              ];
              
              People.sort(dynamicMultiSort(['Name','name'], ['Name', '-surname']));
              // Results in...
              // [ { Name: { name: 'AAA', surname: 'ZZZ' }, Middlename: 'Abrams' },
              //   { Name: { name: 'Name', surname: 'Surname' }, Middlename: 'JJ' },
              //   { Name: { name: 'Name', surname: 'AAA' }, Middlename: 'Wars' } ]
              
              // same logic as above, but strong deviation for dynamic properties 
              function dynamicSort(properties) {
                var sortOrder = 1;
                // determine sort order by checking sign of last element of array
                if(properties[properties.length - 1][0] === "-") {
                  sortOrder = -1;
                  // Chop off sign
                  properties[properties.length - 1] = properties[properties.length - 1].substr(1);
                }
                return function (a,b) {
                  propertyOfA = recurseObjProp(a, properties)
                  propertyOfB = recurseObjProp(b, properties)
                  var result = (propertyOfA < propertyOfB) ? -1 : (propertyOfA > propertyOfB) ? 1 : 0;
                  return result * sortOrder;
                };
              }
              
              /**
               * Takes an object and recurses down the tree to a target leaf and returns it value
               * @param  {Object} root - Object to be traversed.
               * @param  {Array} leafs - Array of downwards traversal. To access the value: {parent:{ child: 'value'}} -> ['parent','child']
               * @param  {Number} index - Must not be set, since it is implicit.
               * @return {String|Number}       The property, which is to be compared by sort.
               */
              function recurseObjProp(root, leafs, index) {
                index ? index : index = 0
                var upper = root
                // walk down one level
                lower = upper[leafs[index]]
                // Check if last leaf has been hit by having gone one step too far.
                // If so, return result from last step.
                if (!lower) {
                  return upper
                }
                // Else: recurse!
                index++
                // HINT: Bug was here, for not explicitly returning function
                // https://stackoverflow.com/a/17528613/3580261
                return recurseObjProp(lower, leafs, index)
              }
              
              /**
               * Multi-sort your array by a set of properties
               * @param {...Array} Arrays to access values in the form of: {parent:{ child: 'value'}} -> ['parent','child']
               * @return {Number} Number - number for sort algorithm
               */
              function dynamicMultiSort() {
                var args = Array.prototype.slice.call(arguments); // slight deviation to base
              
                return function (a, b) {
                  var i = 0, result = 0, numberOfProperties = args.length;
                  // REVIEW: slightly verbose; maybe no way around because of `.sort`-'s nature
                  // Consider: `.forEach()`
                  while(result === 0 && i < numberOfProperties) {
                    result = dynamicSort(args[i])(a, b);
                    i++;
                  }
                  return result;
                }
              }
              

              Example

              Working example on JSBin

              【讨论】:

              • Why? This is not the answer to original question and "the goal" could be solved simply with People.sort((a,b)=>{ return a.Name.name.localeCompare(b.Name.name) || a.Name.surname.localeCompare(b.Name.surname) })
              【解决方案21】:

              One more option:

              var someArray = [...];
              
              function generateSortFn(prop, reverse) {
                  return function (a, b) {
                      if (a[prop] < b[prop]) return reverse ? 1 : -1;
                      if (a[prop] > b[prop]) return reverse ? -1 : 1;
                      return 0;
                  };
              }
              
              someArray.sort(generateSortFn('name', true));
              

              sorts ascending by default.

              【讨论】:

              • Slightly changed version for sorting by multiple fields is here if needed: stackoverflow.com/questions/6913512/…
              • it looks like it could be the next one: export function generateSortFn( prop: string, reverse: boolean = false ): (...args: any) => number { return (a, b) => { return a[prop] < b[prop] ? reverse ? 1 : -1 : a[prop] > b[prop] ? reverse ? -1 : 1 : 0; }; }
              • agreed, but in some case i haven't got needs to look at utility functions.
              【解决方案22】:

              A simple way:

              objs.sort(function(a,b) {
                return b.last_nom.toLowerCase() < a.last_nom.toLowerCase();
              });
              

              See that '.toLowerCase()' is necessary to prevent erros in comparing strings.

              【讨论】:

              • You could use arrow functions to let the code a little more elegant: objs.sort( (a,b) =&gt; b.last_nom.toLowerCase() &lt; a.last_nom.toLowerCase() );
              • This is wrong for the same reason as explained here.
              • Arrow functions are not ES5-worthy. Tons of engines still are restricted to ES5. In my case, I find the answer above significantly better since I'm on an ES5 engine (forced by my company)
              【解决方案23】:

              A simple function that sort an array of object by a property

              function sortArray(array, property, direction) {
                  direction = direction || 1;
                  array.sort(function compare(a, b) {
                      let comparison = 0;
                      if (a[property] > b[property]) {
                          comparison = 1 * direction;
                      } else if (a[property] < b[property]) {
                          comparison = -1 * direction;
                      }
                      return comparison;
                  });
                  return array; // Chainable
              }
              

              Usage:

              var objs = [ 
                  { first_nom: 'Lazslo', last_nom: 'Jamf'     },
                  { first_nom: 'Pig',    last_nom: 'Bodine'   },
                  { first_nom: 'Pirate', last_nom: 'Prentice' }
              ];
              
              sortArray(objs, "last_nom"); // Asc
              sortArray(objs, "last_nom", -1); // Desc
              

              【讨论】:

              • This solution worked perfectly for me to sort bi-directional. Thank u
              【解决方案24】:

              This is my take on this:

              The order parameter is optional and defaults to "ASC" for ascending order.

              Works onaccented chars and it's case insensitive.

              NOTE:It sorts and returns theORIGINALarray.

              function sanitizeToSort(str) {
                return str
                  .normalize('NFD')                   // REMOVE ACCENTED AND DIACRITICS
                  .replace(/[u0300-u036f]/g,'')     // REMOVE ACCENTED AND DIACRITICS
                  .toLowerCase()                      // SORT WILL BE CASE INSENSITIVE
                ;
              }
              
              function sortByProperty(arr, property, order="ASC") {
                arr.forEach((item) => item.tempProp = sanitizeToSort(item[property]));
                arr.sort((a,b) => order === "ASC" ?
                    a.tempProp > b.tempProp ?  1 : a.tempProp < b.tempProp ? -1 : 0
                  : a.tempProp > b.tempProp ? -1 : a.tempProp < b.tempProp ?  1 : 0
                );
                arr.forEach((item) => delete item.tempProp);
                return arr;
              }
              

              SNIPPET

              function sanitizeToSort(str) {
                return str
                  .normalize('NFD')                   // REMOVE ACCENTED CHARS
                  .replace(/[u0300-u036f]/g,'')     // REMOVE DIACRITICS
                  .toLowerCase()
                ;
              }
              
              function sortByProperty(arr, property, order="ASC") {
                arr.forEach((item) => item.tempProp = sanitizeToSort(item[property]));
                arr.sort((a,b) => order === "ASC" ?
                    a.tempProp > b.tempProp ?  1 : a.tempProp < b.tempProp ? -1 : 0
                  : a.tempProp > b.tempProp ? -1 : a.tempProp < b.tempProp ?  1 : 0
                );
                arr.forEach((item) => delete item.tempProp);
                return arr;
              }
              
              const rockStars = [
                { name: "Axl",
                  lastname: "Rose" },
                { name: "Elthon",
                  lastname: "John" },
                { name: "Paul",
                  lastname: "McCartney" },
                { name: "Lou",
                  lastname: "Reed" },
                { name: "freddie",             // WORKS ON LOWER/UPPER CASE
                  lastname: "mercury" },
                { name: "Ámy",                 // WORKS ON ACCENTED CHARS TOO
                  lastname: "winehouse"}
                
              ];
              
              sortByProperty(rockStars,"name");
              
              console.log("Ordered by name A-Z:");
              rockStars.forEach((item) => console.log(item.name + " " + item.lastname));
              
              sortByProperty(rockStars,"lastname","DESC");
              
              console.log("
              Ordered by lastname Z-A:");
              rockStars.forEach((item) => console.log(item.lastname + ", " + item.name));

              【讨论】:

              • not working if the list contain name in the combination of uppercase and lowercase character
              • @AnkeshPandey Thanks for pointing that out. I've fixed it.
              【解决方案25】:

              Warning!
              Using this solution isnot recommendedas it does not result in a sorted array. It is being left here for future reference, because the idea is not rare.

              objs.sort(function(a,b){return b.last_nom>a.last_nom})
              

              【讨论】:

              • Actually it didn't seem to work, had to use the accepted answer. It wasn't sorting correctly.
              【解决方案26】:

              Combining Ege's dynamic solution with Vinay's idea, you get a nice robust solution:

              Array.prototype.sortBy = function() {
                function _sortByAttr(attr) {
                  var sortOrder = 1;
                  if (attr[0] == "-") {
                    sortOrder = -1;
                    attr = attr.substr(1);
                  }
                  return function(a, b) {
                    var result = (a[attr] < b[attr]) ? -1 : (a[attr] > b[attr]) ? 1 : 0;
                    return result * sortOrder;
                  }
                }
              
                function _getSortFunc() {
                  if (arguments.length == 0) {
                    throw "Zero length arguments not allowed for Array.sortBy()";
                  }
                  var args = arguments;
                  return function(a, b) {
                    for (var result = 0, i = 0; result == 0 && i < args.length; i++) {
                      result = _sortByAttr(args[i])(a, b);
                    }
                    return result;
                  }
                }
                return this.sort(_getSortFunc.apply(null, arguments));
              }
              
              Usage:
              
                // Utility for printing objects
                Array.prototype.print = function(title) {
                  console.log("************************************************************************");
                  console.log("**** " + title);
                  console.log("************************************************************************");
                  for (var i = 0; i < this.length; i++) {
                    console.log("Name: " + this[i].FirstName, this[i].LastName, "Age: " + this[i].Age);
                  }
                }
              
              // Setup sample data
              var arrObj = [{
                  FirstName: "Zach",
                  LastName: "Emergency",
                  Age: 35
                },
                {
                  FirstName: "Nancy",
                  LastName: "Nurse",
                  Age: 27
                },
                {
                  FirstName: "Ethel",
                  LastName: "Emergency",
                  Age: 42
                },
                {
                  FirstName: "Nina",
                  LastName: "Nurse",
                  Age: 48
                },
                {
                  FirstName: "Anthony",
                  LastName: "Emergency",
                  Age: 44
                },
                {
                  FirstName: "Nina",
                  LastName: "Nurse",
                  Age: 32
                },
                {
                  FirstName: "Ed",
                  LastName: "Emergency",
                  Age: 28
                },
                {
                  FirstName: "Peter",
                  LastName: "Physician",
                  Age: 58
                },
                {
                  FirstName: "Al",
                  LastName: "Emergency",
                  Age: 51
                },
                {
                  FirstName: "Ruth",
                  LastName: "Registration",
                  Age: 62
                },
                {
                  FirstName: "Ed",
                  LastName: "Emergency",
                  Age: 38
                },
                {
                  FirstName: "Tammy",
                  LastName: "Triage",
                  Age: 29
                },
                {
                  FirstName: "Alan",
                  LastName: "Emergency",
                  Age: 60
                },
                {
                  FirstName: "Nina",
                  LastName: "Nurse",
                  Age: 54
                }
              ];
              
              //Unit Tests
              arrObj.sortBy("LastName").print("LastName Ascending");
              arrObj.sortBy("-LastName").print("LastName Descending");
              arrObj.sortBy("LastName", "FirstName", "-Age").print("LastName Ascending, FirstName Ascending, Age Descending");
              arrObj.sortBy("-FirstName", "Age").print("FirstName Descending, Age Ascending");
              arrObj.sortBy("-Age").print("Age Descending");

              【讨论】:

              • Thanks for the idea! By the way, please do not encourage people to change the Array Prototype (see the warning at the end of my example).
              【解决方案27】:

              additional desc params forEge Özcancode

              function dynamicSort(property, desc) {
                  if (desc) {
                      return function (a, b) {
                          return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
                      }   
                  }
                  return function (a, b) {
                      return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
                  }
              }
              

              【讨论】:

                【解决方案28】:

                Given the original example:

                var objs = [ 
                    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
                    { first_nom: 'Pig',    last_nom: 'Bodine'   },
                    { first_nom: 'Pirate', last_nom: 'Prentice' }
                ];
                

                Sort by multiple fields:

                objs.sort(function(left, right) {
                    var last_nom_order = left.last_nom.localeCompare(right.last_nom);
                    var first_nom_order = left.first_nom.localeCompare(right.first_nom);
                    return last_nom_order || first_nom_order;
                });
                

                Notes

                • a.localeCompare(b) is universally supported and returns -1,0,1 if a&lt;b,a==b,a&gt;b respectively.
                • || in the last line gives last_nom priority over first_nom.
                • Subtraction works on numeric fields: var age_order = left.age - right.age;
                • Negate to reverse order, return -last_nom_order || -first_nom_order || -age_order;

                【讨论】:

                  【解决方案29】:
                  function compare(propName) {
                      return function(a,b) {
                          if (a[propName] < b[propName])
                              return -1;
                          if (a[propName] > b[propName])
                              return 1;
                          return 0;
                      };
                  }
                  
                  objs.sort(compare("last_nom"));
                  

                  【讨论】:

                  • Please consider editing your post to add more explanation about what your code does and why it will solve the problem. An answer that mostly just contains code (even if it's working) usually wont help the OP to understand their problem.
                  【解决方案30】:

                  Using Ramda,

                  npm install ramda

                  import R from 'ramda'
                  var objs = [ 
                      { first_nom: 'Lazslo', last_nom: 'Jamf'     },
                      { first_nom: 'Pig',    last_nom: 'Bodine'   },
                      { first_nom: 'Pirate', last_nom: 'Prentice' }
                  ];
                  var ascendingSortedObjs = R.sortBy(R.prop('last_nom'), objs)
                  var descendingSortedObjs = R.reverse(ascendingSortedObjs)
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-03-13
                    • 1970-01-01
                    • 2022-07-28
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多