Предположим есть <code>.sort((a, b) => a.position - b.position)</code>. Если у какого‑то элемента отсутствует свойство position, он остаётся на той же позиции после сортировки. Гарантировано ли такое поведение во всех реализациях JavaScript/всех средах, или в каких‑то случаях возможны некорректные или неопределённые результаты?
Короткий ответ — нет, такое поведение не гарантируется во всех реализациях и на всех входных данных.
Почему:
- Выражение a.position - b.position даёт NaN, если хотя бы одно из position равно undefined (или не приводимо к числу). Когда компаратор возвращает NaN, поведение сравнения становится «равным/неопределённым» — реализация сортировки может трактовать это по‑разному. В современных движках NaN фактически ведёт себя как «0/equal», но это не было строго гарантировано исторически.
- С ES2019 ECMAScript требует стабильную sort(), поэтому в современных окружениях (современный V8, SpiderMonkey, JavaScriptCore и т.п.) равные элементы сохраняют относительный порядок. До этого стабильность не гарантировалась, и порядок равных элементов мог меняться.
- Дополнительные подводные камни:
- Если position иногда — BigInt, а вы делаете вычитание с Number, это вызовет TypeError.
- Если position — нечисловая строка, вычитание приведёт к NaN.
- Для «дыр» в массиве (sparse arrays) и отсутствующих элементов поведение тоже зависит от реализации.
Рекомендации (надёжный вариант):
1) Явно обработать отсутствие позиции и NaN, и вернуть -1/0/1, а не число-результат вычитания:
arr.sort((a,b) => {
const pa = (a && a.position != null) ? Number(a.position) : Infinity;
const pb = (b && b.position != null) ? Number(b.position) : Infinity;
if (Number.isNaN(pa) && Number.isNaN(pb)) return 0;
if (Number.isNaN(pa)) return 1;
if (Number.isNaN(pb)) return -1;
return pa < pb ? -1 : (pa > pb ? 1 : 0);
});
2) Или проще, если хотите, чтобы элементы без position оставались после тех, у кого есть position:
const toNum = v => (v == null ? Infinity : Number(v));
arr.sort((a,b) => toNum(a.position) - toNum(b.position)); // но следите за NaN/BigInt
3) Если вам нужно гарантированно сохранять исходный порядок при «равных» значениях в любых средах, можно присоединить индекс:
arr.map((x,i) => ({x,i})).sort((A,B) => {
const r = comparePositions(A.x,B.x);
return r !== 0 ? r : A.i - B.i;
}).map(o => o.x);
Вывод: в современных JS‑движках ваш код, вероятно, «будет работать» и элементы без position останутся на своих местах благодаря стабильности и тому, что NaN воспринимают как «равно». Но это не строго гарантируется для всех сред и для всех видов значений (NaN, BigInt и т.п.). Лучше явно обрабатывать отсутствующие/нечисловые значения в компараторе.
в современных движках (ES2019+) по спеку работает: NaN из компаратора трактуется как 0, sort стабильный — элементы без position сохранят порядок. но это неявное поведение, а в старых окружениях может быть иначе. лучше явно: <pre><code>(a.position ?? Infinity) - (b.position ?? Infinity)</code></pre>
<blockquote><br/>
A negative value indicates that a should come before b.<br/>
A positive value indicates that a should come after b.<br/>
Zero or NaN indicates that a and b are considered equal.<br/>
</blockquote> <br/> <br/> <blockquote><br/>
More formally, the comparator is expected to have the following properties, in order to ensure proper sort behavior:<br/>
<br/>
Pure: The comparator does not mutate the objects being compared or any external state. (This is important because there's no guarantee when and how the comparator will be called, so any particular call should not produce visible effects to the outside.)<br/>
Stable: The comparator returns the same result with the same pair of input.<br/>
Reflexive: compareFn(a, a) === 0.<br/>
Anti-symmetric: compareFn(a, b) and compareFn(b, a) must both be 0 or have opposite signs.<br/>
Transitive: If compareFn(a, b) and compareFn(b, c) are both positive, zero, or negative, then compareFn(a, c) has the same positivity as the previous two.<br/>
</blockquote> <br/> <br/> Если слева, справа или с обоих сторон position будет undefined, то в результате будет NaN
Если надо элементы без position оставлять на своих местах, то можно собрать все элементы с position в отдельный массив, отсортировать его, потом уже в новом порядке вернуть в исходный массив. Как-то так (не дебажил): <br/> <br/> <pre><code class="javascript">const arrWithPos = arr.filter(x => x.position != null);
arrWithPos.sort((a, b) => a.position - b.position);
let p = 0;
for (let i = 0; i < arr.length; ++i) {
if (arr[i].position != null) {
arr[i] = arrWithPos[p];
p++;
}
}</code></pre>