I am using anArrayOfObjects.sort((a, b) => a.value - b.value)
, where some objects don't have a value
property.
That leads to different results in Firefox and Chrome, where Chrome seems to sort the object(s) with no value
property/undefined value to the end, Firefox doesn't.
Is the spec not prescribing the result that Chrome gives, meaning the Firefox result is wrong? Or is that part of the sort
result up to a particular implementation?
const data2 = [
{ 'name' : 'a', 'value' : 5 },
{ 'name' : 'b', 'value' : 2 },
{ 'name' : 'c' },
{ 'name' : 'd', 'value' : 1 }
];
console.log('before sorting: ', data2);
data2.sort((a, b) => a.value - b.value);
console.log('after sorting: ', data2);
CodePudding user response:
Neither are "wrong".
undefined - undefined
, undefined - 1
and 1 - undefined
all return NaN
, and NaN
compared to something is always false
.
The difference between the 2 browsers is probably due to sorting implementation.
The used sorting algorithm can give different results, depending on the order of values beforehand, and how the implementation deals with NaN
.
CodePudding user response:
Don't allow browsers engines to decide how to handle your code in doubtful situations, just write your code without it:
data2.sort((a, b) => (a?.value || 0) - (b?.value || 0));
(Default value could be some small or big number to sort with order you need.)
CodePudding user response:
The compare function works as follows:
compareFn(a, b) return value |
sort order |
---|---|
< 0 |
sort a after b |
> 0 |
sort a before b |
=== 0 |
keep original order of a and b |
Any subtractions involving undefined
give a return value of NaN
, which per the spec, is immediately converted to 0
. However, the order will depend on the specific algorithm used by the implementation, as that determines which values get compared in this way.
The issue becomes clearer when you log out the comparisons:
const data2 = [
{ 'name' : 'a', 'value' : 5 },
{ 'name' : 'b', 'value' : 2 },
{ 'name' : 'c' },
{ 'name' : 'd', 'value' : 1 }
];
console.log('before sorting: ', data2);
data2.sort((a, b) => (console.log(a.value, b.value), a.value - b.value));
console.log('after sorting: ', data2);
Firefox seems to be asking
> 0
:5 2 5 undefined undefined 1
a
is'a'
andb
is'b'
, return value is3
so'a'
is must be after'b'
.a
is'a'
andb
is'c'
, return value is0
so'a'
stays before'c'
.a
is'c'
andb
is'd'
, return value is0
so'c'
stays before'd'
.
Chrome seems to be asking
< 0
:2 5 undefined 2 undefined 5 1 5 1 2
a
is'b'
andb
is'a'
, return value is-3
so'b'
must be before'a'
.a
is'c'
andb
is'b'
, return value is0
so'c'
stays after'b'
a
is'c'
andb
is'a'
, return value is0
so'c'
stays after'a'
a
is'd'
andb
is'a'
, return value is-4
so'd'
must be before'a'
a
is'd'
andb
is'b'
, return value is-1
so'd'
must be before'b'
Specifically Chrome doesn't ever compare 'c'
and 'd'
directly, so is never told to keep them in the same order.
CodePudding user response:
It is widely known that sorting algorithms are browsers implementation specific, moreover, since there are no silver bullet algorithms, a certain browser may use different sorting algorithms for different data shapes as a optimization technique.
This became obvious when looking at the following example results in Firefox:
const arr1 = [
{ 'value' : 5 },
{ 'value' : 2 },
{ 'value': undefined },
{ 'value' : 4 },
{ 'value' : 3 },
{ 'value': undefined },
{ 'value' : 1 },
{ 'value' : 0 }
];
const arr2 = [5, 2, undefined, 4, 3, undefined, 1, 0];
arr1.sort((a, b) => a.value - b.value);
arr2.sort((a, b) => a - b);
console.log(arr1.map(v => `${v.value}`).join());
console.log(arr2.map(v => `${v}`).join());
In fact, Chrome results for the above examples are compliant with the specs:
- Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
a. If x and y are both undefined, return 0