Sorting an array of objects (by a property of type number) doesn't return a sorted result like for an array of numbers.
Why so ?
How to make it sort like for numbers ?
Demo: Sorting an array of numbers
const sorted = [0, 5, 2, undefined, 3, 1, 4]
.sort((a, b) => a - b);
console.log(sorted);
Demo: Sorting an array of objects
const notSorted = [
{i:0},
{i:5},
{i:2},
{i: undefined},
{i:3},
{i:1},
{i:4},
]
.sort((a, b) => a.i - b.i)
.map(a => a.i);
console.log(notSorted);
I am currently on Chrome 90. Maybe some other browsers or engines don't have this issue. Tell me.
This is because JavaScript's sort() method converts each item in the array into strings and constructs the sequence by comparing each array item based on the UTF-16 code values when there is no callback specified.
To sort an array of objects, use the sort() method with a compare function. A compareFunction applies rules to sort arrays by defined our own logic. They allow us to sort arrays of objects by strings, integers, dates, or any other custom property.
To sort an array of objects, you use the sort() method and provide a comparison function that determines the order of objects.
According to the spec:
- If x and y are both undefined, return +0.
- If x is undefined, return 1.
- If y is undefined, return −1.
- If the argument comparefn is not undefined, then
- Let v be ToNumber(Call(comparefn, undefined, «x, y»)).
- ReturnIfAbrupt(v).
- If v is NaN, return +0.
- Return v.
That explains why it works in the first case because the sorted values are not enclosed in an object. In the second case the values are not undefined
(only the properties are) so the native undefined
handling of Array.prototype.sort()
doesn't take over, which means the callback is being executed even if a.i
or b.i
is undefined
, and it returns NaN
(not a number).
As the callback returns NaN
for every undefined
property, they are considered equal to every other item. That leads to an erratic behavior, which depends on the actual algorithm of Array.prototype.sort()
in the JavaScript engine.
Here are the return values of the question example for some browsers:
[0, 1, 2, 5, undefined, 3, 4]
[0, 1, 2, 3, 5, undefined, 4]
[0, 2, 5, undefined, 1, 3, 4]
Your sorting algorithm produces, in some instances, NaN
, since undefined - someNum
and someNum - undefined
both result in NaN
. This means that your callback is not consistent, which means that the resulting sort order is implementation-defined.
A function comparefn is a consistent comparison function for a set of values S if all of the requirements below are met for all values a, b, and c (possibly the same value) in the set S: The notation a <CF b means comparefn(a, b) < 0; a =CF b means comparefn(a, b) = 0 (of either sign); and a >CF b means comparefn(a, b) > 0.
- Calling comparefn(a, b) always returns the same value v when given a specific pair of values a and b as its two arguments. Furthermore, Type(v) is Number, and v is not NaN. Note that this implies that exactly one of a <CF b, a =CF b, and a >CF b will be true for a given pair of a and b.
If you ever return NaN
from a .sort
callback, your results can be anything at all: the behavior in such a case is undefined by the specification (though certain implementations might produce a result that makes more intuitive sense... or not). So, make sure never to return NaN
. In this case, explicitly test to see if the .i
property being iterated over is undefined
, and substitute a different value for it - maybe Infinity or -Infinity.
const sanitize = val => val === undefined ? Infinity : val;
const notSorted = [
{i:0},
{i:5},
{i:2},
{i: undefined},
{i:3},
{i:1},
{i:4},
]
.sort((a, b) => sanitize(a.i) - sanitize(b.i))
.map(a => a.i);
console.log(notSorted);
Because you got an object with an undefined
property in that array, on which your comparison function is not consistent. You'll need to ensure that it returns a number, not NaN
. Chrome uses different algorithms for sorting number arrays vs object arrays, and that you got lucky in one case doesn't mean it would always work. It does work as expected with the array of plain numbers, since .sort()
ignores undefined
array elements (not attempting to compare them against something else) and always puts them at the end of the array.
You can fix it by doing
.sort((a, b) => (a.i ?? -Infinity) - (b.i ?? -Infinity))
(or +Infinity
, depending on whether you want your undefined
values first or last).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With