Сортировка массивов объектов

Сортировка массивов объектов в JavaScript часто упирается не в алгоритм sort, а в правила сравнения строковых значений. Обычное лексикографическое сравнение Unicode-кодов приводит к неожиданным результатам: регистр влияет на порядок, диакритика ломает ожидаемую последовательность, числовые строки сортируются некорректно.

const users = [
  { name: "zebra" },
  { name: "Änne" },
  { name: "anna" },
  { name: "Álvaro" }
];

users.sort((a, b) => a.name.localeCompare(b.name));

Метод localeCompare уже учитывает локаль, но его прямое использование в каждом сравнении может быть избыточным по производительности и недостаточно гибким для сложных сценариев сортировки.


Intl.Collator как основа локализованной сортировки

Объект Intl.Collator предоставляет специализированный механизм сравнения строк с учётом языковых правил. Он позволяет заранее создать функцию сравнения, оптимизированную под конкретную локаль и параметры.

const collator = new Intl.Collator("ru", { sensitivity: "base" });

const words = ["ёж", "елка", "яблоко", "Ель"];
words.sort(collator.compare);

Ключевые параметры Intl.Collator

locale — определяет правила языка сортировки options — управляет поведением сравнения:

  • sensitivity

    • "base" — игнорирует регистр и диакритику
    • "accent" — учитывает диакритику, игнорирует регистр
    • "case" — учитывает регистр, игнорирует диакритику
    • "variant" — строгая сортировка
  • numeric

    • true — корректная сортировка чисел внутри строк
const collator = new Intl.Collator("en", { numeric: true });

["item2", "item10", "item1"].sort(collator.compare);
// item1, item2, item10

Сортировка массива объектов по строковому полю

Наиболее частый сценарий — сортировка объектов по имени, названию, заголовку.

const products = [
  { title: "iPhone 12" },
  { title: "iPhone 5" },
  { title: "iPhone 11" }
];

const collator = new Intl.Collator("en", { numeric: true });

products.sort((a, b) => collator.compare(a.title, b.title));

Использование Collator предпочтительнее localeCompare в цикле сортировки, так как сравнение уже оптимизировано и не создаёт повторных вычислений локали.


Многоуровневая сортировка объектов

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

const users = [
  { firstName: "Ivan", lastName: "Petrov" },
  { firstName: "Anna", lastName: "Petrova" },
  { firstName: "Ivan", lastName: "Ivanov" }
];

const collator = new Intl.Collator("ru");

users.sort((a, b) => {
  const last = collator.compare(a.lastName, b.lastName);
  if (last !== 0) return last;

  return collator.compare(a.firstName, b.firstName);
});

Такая схема обеспечивает детерминированную сортировку без зависимости от порядка исходных данных.


Стабильность сортировки и влияние Intl

Современные реализации JavaScript гарантируют стабильность Array.prototype.sort, однако поведение зависит от движка. При использовании Intl.Collator важно учитывать, что:

  • сравнение не должно быть «тяжёлым» внутри сортировки
  • создание Collator внутри sort ухудшает производительность
  • один экземпляр Collator должен использоваться повторно
// плохая практика
array.sort((a, b) =>
  new Intl.Collator("en").compare(a.name, b.name)
);

// корректная практика
const collator = new Intl.Collator("en");
array.sort((a, b) => collator.compare(a.name, b.name));

Числовая сортировка строковых полей

В данных часто встречаются строки с числами: версии, артикулы, идентификаторы.

Без numeric: true сортировка ломается:

["v1", "v10", "v2"].sort();
// v1, v10, v2

С Intl.Collator:

const collator = new Intl.Collator("en", { numeric: true });

["v1", "v10", "v2"].sort(collator.compare);
// v1, v2, v10

Это особенно важно для версионирования и списков файлов.


Управление чувствительностью сортировки

Параметр sensitivity влияет на то, как строки различаются:

const collator = new Intl.Collator("en", { sensitivity: "base" });

["a", "A", "á", "b"].sort(collator.compare);
  • "base" — группирует визуально одинаковые символы
  • "accent" — различает акценты
  • "case" — различает регистр
  • "variant" — учитывает всё

Выбор зависит от доменной логики. Для UI-списков часто используется "base", для юридических или точных данных — "variant".


Сортировка по нескольким локализованным полям с Intl

Когда несколько полей требуют локализованного сравнения, применяется комбинирование:

const collator = new Intl.Collator("ru");

const data = [
  { city: "Москва", street: "Арбат" },
  { city: "Москва", street: "Бауманская" },
  { city: "Астана", street: "Абая" }
];

data.sort((a, b) => {
  const cityCompare = collator.compare(a.city, b.city);
  if (cityCompare !== 0) return cityCompare;

  return collator.compare(a.street, b.street);
});

Intl.Collator и производительность сортировки

При сортировке больших массивов критично минимизировать количество сравнений и тяжёлых операций:

  • создание Intl.Collator выполняется один раз
  • метод compare используется как чистая функция
  • исключаются дополнительные преобразования строк внутри сортировки

Оптимизированный вариант:

const collator = new Intl.Collator("ru", {
  numeric: true,
  sensitivity: "base"
});

const sorted = items.sort((a, b) =>
  collator.compare(a.label, b.label)
);

Использование с динамическими данными интерфейса

В прикладных сценариях сортировка часто зависит от пользовательских настроек: язык интерфейса, режим отображения, тип сортировки.

function sortUsers(users, locale) {
  const collator = new Intl.Collator(locale, {
    numeric: true,
    sensitivity: "base"
  });

  return users.sort((a, b) => collator.compare(a.name, b.name));
}

При смене языка интерфейса создаётся новый Collator, соответствующий выбранной локали.


Intl.Collator в контексте других Intl API

Сортировка часто комбинируется с другими объектами Intl:

  • Intl.DateTimeFormat — сортировка по форматированным датам
  • Intl.NumberFormat — отображение чисел при сортировке
  • Intl.PluralRules — управление множественными формами при выводе списков

Пример сортировки по датам:

const formatter = new Intl.DateTimeFormat("ru");

events.sort((a, b) =>
  new Date(a.date) - new Date(b.date)
);

Хотя здесь Intl.Collator не применяется напрямую, принцип локализации сохраняется: данные сортируются в соответствии с культурным контекстом отображения.


Обработка смешанных строк и чисел в объектах

В реальных структурах данные часто неоднородны:

const items = [
  { label: "file2" },
  { label: "file10" },
  { label: "file1" }
];

Правильная сортировка:

const collator = new Intl.Collator("en", { numeric: true });

items.sort((a, b) => collator.compare(a.label, b.label));

Это устраняет необходимость писать собственные парсеры чисел внутри строк.


Ограничения и особенности поведения

При использовании Intl.Collator важно учитывать:

  • поведение зависит от реализации движка (V8, SpiderMonkey)
  • локализация может отличаться по версиям ICU
  • сложные языки имеют специфические правила сортировки
  • сортировка не всегда транзитивна при некорректных настройках sensitivity

Несмотря на это, Intl.Collator остаётся стандартным и наиболее надёжным инструментом для локализованного сравнения строк в JavaScript.


Практическая модель построения сортировки объектов

Универсальный шаблон сортировки объектов с локализацией:

const collator = new Intl.Collator(locale, options);

function sortByField(array, field) {
  return array.sort((a, b) =>
    collator.compare(a[field], b[field])
  );
}

Расширение для нескольких полей:

function multiSort(a, b) {
  let res = collator.compare(a.lastName, b.lastName);
  if (res !== 0) return res;

  res = collator.compare(a.firstName, b.firstName);
  if (res !== 0) return res;

  return collator.compare(a.middleName, b.middleName);
}

Такая структура масштабируется на сложные модели данных без потери читаемости и предсказуемости результата.