egor agegorin.

Внезапный программистский контент:

TLDR: Если в JS/TS нужно отсортировать массив чисел, которые представлены строками, то можно использовать localeCompare с параметром numeric: true.

Возможно вы знаете что JS:

  1. Умеет сравнивать строки
  2. Сравнивает строки посимвольно, начиная с первого символа
  3. Сравнивает символы по их числовому коду

Это сделано чтобы можно было сортировать списки имен/названий:

['John', 'Ben', 'Alice'].sort() // ['Alice', 'Ben', 'John']

У этого есть отдельные проблемы, например буква Ё не находится между буквами Е и Ж:

['Лиса', 'Ёж', 'Волк', 'Аист'].sort() // ['Ёж', 'Аист', 'Волк', 'Лиса']

Но так же это не работает нормально, если у вас числа представлены строками:

['10','2','1'].sort() // ['1', '10', '2']

И в целом это даже логично, так как строго следует простому алгоритму. Но неудобно в реальности.

Можно конечно явно описать функцию сравнения, и там преобразовывать строки в числа:

['10','2','1'].sort((a, b) => a - b) // ['1', '2', '10']

Но у такого подхода есть огромная проблема: числа могут выйти за пределы безопасных значений и все сломается:

['9007199254740993','9007199254740991','9007199254740992'].sort((a, b) => a - b) // ['9007199254740991', '9007199254740993', '9007199254740992']

А такое может случиться, если на бэкенде используются int64 для id-шников сущностей. В целом иногда сортировать большие числа все-таки надо, поэтому в актуальном JS-е есть метод localCompare, который умеет это делать!

116457 < 3085 // false
"116457" < "3085" // true
"116457".localeCompare("3085") // -1
"116457".localeCompare("3085", undefined, { numeric: true }) // 1
['10','2','1'].sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) // ['1', '2', '10']
['9007199254740993','9007199254740991','9007199254740992'].sort((a, b) => a.localeCompare(b, undefined, { numeric: true })) // ['9007199254740991', '9007199254740992', '9007199254740993']

Описание на MDN

20 июня 2025 г.
code, javascript
by agegorin