---
title: "文件列表为什么把 10 排在 2 前面：用 Intl.Collator 定义排序"
date: 2026-11-13
slug: javascript-intl-collation-sort
canonical: https://kernelranch.com/blog/2026/11/javascript-intl-collation-sort
markdown_url: https://kernelranch.com/blog/2026/11/javascript-intl-collation-sort.md
tags: ["javascript", "i18n", "sorting"]
---文件列表里的 `file-10` 排在 `file-2` 前面，通常是因为使用了默认字符串排序。数字片段可用 `Intl.Collator` 的 `numeric` 选项比较。人名或目录标题还要显式选择语言，因为不同语言对同一字母的排序规则可能不同。

## 1. 本机复现

保存为 `collation_lab.mjs`，运行 `node collation_lab.mjs`。样本包含数字文件名、带变音符的字母，以及主排序规则下等价的两个标签。

```javascript
import assert from 'node:assert/strict';

const names = ['file-2', 'file-10', 'file-1'];
const natural = new Intl.Collator('en', { numeric: true });
const plain = [...names].sort();
const numeric = [...names].sort(natural.compare);
assert.deepEqual(plain, ['file-1', 'file-10', 'file-2']);
assert.deepEqual(numeric, ['file-1', 'file-2', 'file-10']);
console.log('default:', plain.join(', '));
console.log('numeric:', numeric.join(', '));

for (const [locale, expected] of [
  ['de', ['a', 'ä', 'z']],
  ['sv', ['a', 'z', 'ä']],
]) {
  const collator = new Intl.Collator(locale);
  const letters = ['z', 'ä', 'a'].sort(collator.compare);
  assert.deepEqual(letters, expected);
  console.log(locale + ':', letters.join(', '));
}

const byLabel = new Intl.Collator('en', { sensitivity: 'base' });
const items = [{ id: 7, label: 'alpha' }, { id: 2, label: 'Alpha' }];
assert.equal(byLabel.compare(items[0].label, items[1].label), 0);
items.sort((a, b) => byLabel.compare(a.label, b.label) || a.id - b.id);
assert.deepEqual(items.map(item => item.id), [2, 7]);
console.log('equal labels, ID order:', items.map(item => item.id).join(', '));
```

本地实测输出如下。

```text
default: file-1, file-10, file-2
numeric: file-1, file-2, file-10
de: a, ä, z
sv: a, z, ä
equal labels, ID order: 2, 7
```

## 2. 让显示顺序与分页顺序一致

默认排序把字符串按 UTF-16 代码单元顺序比较。`Intl.Collator` 会根据生效的语言和选项比较，调用方只应依赖返回值的正负或零。`numeric: true` 适合文件名等数字片段，语义化版本应使用理解主版本、预发布字段等规则的比较器。

展示层可以按用户语言复用已创建的 Collator。服务端分页时应先对完整结果按统一规则排序，再取一页。浏览器拿到一页后自行重排，只能改变这一页的内部次序。主键等唯一字段可以为比较结果为零的记录补上确定顺序。对跨环境要求完全一致的排序，还要固定参与排序的语言数据和版本。

## 小结

给数字片段和本地化文本选定比较规则。分页使用同一排序规则和唯一的次级键。

## 数据来源与检索时间

检索时间 2026-11-13。

| 来源 | URL | 对应内容 |
|---|---|---|
| ECMA-262 Array.prototype.sort | https://tc39.es/ecma262/#sec-array.prototype.sort | 默认比较与排序行为 |
| ECMA-402 Intl.Collator | https://tc39.es/ecma402/#collator-objects | 语言、数字选项、比较结果及版本差异 |

## 核验说明

- Windows、Node.js v24.19.0 验证数字文件名、德语与瑞典语顺序，以及标签相等时按 ID 排序。观测结果基于该运行时的国际化数据。
