Intl.Collator API
实现跨语言字符串的正确排序与比较
简介
在 JavaScript 中排序字符串,看起来很简单,但一旦涉及国际化文本,事情就没那么容易了。默认的字符串比较基于 Unicode 码点值,这会导致很多语言得到错误的排序结果。Intl.Collator API 提供了遵循本地语言规则的字符串比较功能,能够正确处理特殊字符并尊重文化排序习惯。
默认排序为何失效
以排序德语名字列表为例:
const names = ["Zoe", "Ava", "Ärzte", "Änder"];
console.log(names.sort());
// ["Ava", "Zoe", "Änder", "Ärzte"]
这样的输出对德语使用者来说是错误的。在德语中,带元音变音符的字符比如 ä,应该排在其基础字母 a 附近,而不是放在最后。出现这种问题,是因为 JavaScript 按照 Unicode 码点值来比较字符串,其中 Ä (U+00C4) 排在 Z (U+005A) 之后。
不同语言有不同的排序规则。例如,瑞典语会把 ä 排在字母表末尾,德语会将其排在 a 附近,而法语则对有重音的字符有不同的处理。二进制比较无法反映这些文化差异。
字符串排序机制解析
排序(Collation)就是按照各自语言的规则,对字符串进行比较和排序的过程。Unicode 排序算法定义了如何通过分别分析字符、变音符号、大小写及标点来进行字符串比较。
比较两个字符串时,排序函数会返回一个数值:
- 负值:第一个字符串排在第二个之前
- 零:按当前敏感度级别,两字符串被视为等价
- 正值:第一个字符串排在第二个之后
这种三向比较模式可以直接用于 Array.sort,并且让你能精准控制哪些差异影响排序。
使用 localeCompare 实现基础的本地化排序
localeCompare 方法可以实现本地化的字符串比较:
const names = ["Zoe", "Ava", "Ärzte", "Änder"];
console.log(names.sort((a, b) => a.localeCompare(b, "de")));
// ["Ava", "Änder", "Ärzte", "Zoe"]
这样就能得到正确的德语排序效果。第一个参数指定了区域,localeCompare 会自动遵循相应的文化规则。
你可以将选项作为第三个参数传递:
const items = ["File10", "File2", "File1"];
console.log(items.sort((a, b) =>
a.localeCompare(b, "en", { numeric: true })
));
// ["File1", "File2", "File10"]
numeric 选项可以实现自然排序,使得 "2" 排在 "10" 之前。如果没有该选项,由于 "1" 排在 "2" 前,结果会是 "10" 排在 "2" 前。
重复调用 localeCompare 的性能问题
每次调用 localeCompare 都会从头处理一次区域设置。当需要对大型数组进行排序时,这会带来很大的性能开销:
// Inefficient: processes locale for every comparison
const sorted = items.sort((a, b) => a.localeCompare(b, "de"));
排序 1000 个元素大约需要比较 10000 次。每次比较都会重新创建一次区域配置,极大增加了性能成本。当界面中需要处理大量数据时,这种开销会非常明显。
利用 Intl.Collator 高效地进行字符串比较
Intl.Collator 会创建一个可复用的比较对象,它只需处理一次区域设置:
const collator = new Intl.Collator("de");
const sorted = items.sort((a, b) => collator.compare(a, b));
collator 实例会保存地区设置和比较规则。它的 compare 方法会在每次比较时直接使用这些预先计算好的规则,无需重复初始化。
对于大型数组排序,与反复调用 localeCompare 相比,性能可提升 60% 到 80%。
直接访问 compare 方法
你可以直接将 compare 方法传给 sort:
const collator = new Intl.Collator("de");
const sorted = items.sort(collator.compare);
这样做的原因是 compare 已经绑定在 collator 实例上。该方法会接收两个字符串并返回比较结果,正好符合 Array.sort 所期待的回调格式。
理解敏感度级别
sensitivity 选项用来控制字符串比较时哪些字符差异会被考虑在内。共有四个级别:
基础敏感度
基础敏感度会忽略重音符和大小写:
const collator = new Intl.Collator("en", { sensitivity: "base" });
console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // 0
console.log(collator.compare("a", "A")); // 0
console.log(collator.compare("a", "b")); // -1
只关注基本字母的不同。该级别适合模糊搜索场景,比如用户可能不会准确输入重音符时。
重音敏感度
重音敏感度会区分重音,但忽略大小写:
const collator = new Intl.Collator("en", { sensitivity: "accent" });
console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // -1
console.log(collator.compare("a", "A")); // 0
console.log(collator.compare("á", "A")); // 1
重音符与非重音符的字符是不同的,但同一个字母的大写与小写会被认为相同。
区分大小写
区分大小写时,大小写不同会被区分,但会忽略重音符:
const collator = new Intl.Collator("en", { sensitivity: "case" });
console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // 0
console.log(collator.compare("a", "A")); // -1
console.log(collator.compare("á", "Á")); // -1
会区分大小写,但忽略重音符。这种敏感度级别在实际中较少见。
区分字符变体
字符变体敏感会考虑所有差异:
const collator = new Intl.Collator("en", { sensitivity: "variant" });
console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // -1
console.log(collator.compare("a", "A")); // -1
console.log(collator.compare("á", "Á")); // -1
这是排序的默认方式。每个字符的不同都会产生不同的对比结果。
根据场景选择敏感度
不同的场景需要选择不同的敏感度:
- 排序列表:使用字符变体敏感,确保严格排序
- 内容搜索:使用基本敏感度,不区分重音符或大小写
- 筛选选项:使用重音符敏感度,当大小写不重要时使用
- 区分大小写的搜索:只需大小写敏感,忽略重音符
usage 选项为常见场景提供了默认的敏感度设置。
按用途优化排序和搜索模式
usage 选项可针对排序或搜索优化排序器的行为:
// Optimized for sorting
const sortCollator = new Intl.Collator("en", { usage: "sort" });
// Optimized for searching
const searchCollator = new Intl.Collator("en", { usage: "search" });
排序时,默认使用字符变体敏感,每个差异都会影响顺序。搜索时则优化为更宽松的敏感度以便更容易匹配结果。
要实现不区分大小写和重音符的模糊搜索:
const collator = new Intl.Collator("en", {
usage: "search",
sensitivity: "base"
});
const items = ["Apple", "Äpfel", "Banana"];
const matches = items.filter(item =>
collator.compare(item, "apple") === 0
);
console.log(matches); // ["Apple"]
这种模式让用户在搜索时不需要输入完全匹配的字符,实现模糊匹配。
启用数字排序以实现自然排序
numeric 选项会将字符串中的数字作为数值处理:
const collator = new Intl.Collator("en", { numeric: true });
const files = ["File1", "File10", "File2"];
console.log(files.sort(collator.compare));
// ["File1", "File2", "File10"]
如果不启用数字排序,"File10" 会被排在 "File2" 前面,因为字符串 "10" 的首字母是 "1"。数字排序则会解析数字序列并用数学方式比较。
这样可以让文件名、版本号、编号列表等排序符合人的直观习惯。
使用数字排序处理小数
数字排序在处理小数时存在一个限制:
const collator = new Intl.Collator("en", { numeric: true });
const values = ["1.5", "1.10", "1.2"];
console.log(values.sort(collator.compare));
// ["1.2", "1.5", "1.10"]
小数点会被当作标点符号处理,不作为数字的一部分。每个标点之间的片段会被单独排序。若需对小数进行排序,请先将其解析为数字并用数值比较的方式排序。
通过 caseFirst 控制字母顺序
caseFirst 选项可决定排序时大写字母或小写字母优先:
// Uppercase first
const upperFirst = new Intl.Collator("en", { caseFirst: "upper" });
console.log(["a", "A", "b", "B"].sort(upperFirst.compare));
// ["A", "a", "B", "b"]
// Lowercase first
const lowerFirst = new Intl.Collator("en", { caseFirst: "lower" });
console.log(["a", "A", "b", "B"].sort(lowerFirst.compare));
// ["a", "A", "b", "B"]
默认值为 false,按语言环境的默认顺序排序。当 sensitivity 设置为 base 或 accent 时,该选项无效,因为这两种级别会忽略大小写。
忽略比较时的标点符号
ignorePunctuation 选项可在比较时跳过标点符号:
const collator = new Intl.Collator("en", { ignorePunctuation: true });
console.log(collator.compare("hello", "he-llo")); // 0
console.log(collator.compare("hello", "hello!")); // 0
对于泰语,该选项默认值为 true,其他语言则为 false。当你希望标点符号不影响字符串排序或匹配时,可启用此选项。
针对特定语言指定排序类型
部分语言环境支持多种排序规则以实现专业排序:
// Chinese pinyin ordering
const pinyin = new Intl.Collator("zh-CN-u-co-pinyin");
// German phonebook ordering
const phonebook = new Intl.Collator("de-DE-u-co-phonebk");
// Emoji grouping
const emoji = new Intl.Collator("en-u-co-emoji");
排序类型可在语言环境字符串中使用 Unicode 扩展语法指定。常见类型包括:
pinyin:按中文拼音排序stroke:按中文笔画数排序phonebk:德语电话簿顺序trad:某些语言的传统排序规则emoji:按类别对表情符号分组
请使用 Intl.supportedValuesOf 检查你环境中支持的排序类型。
在应用中复用 Collator 实例
建议创建一次 Collator 实例,在应用各处重复使用:
// utils/collation.js
export const germanCollator = new Intl.Collator("de");
export const searchCollator = new Intl.Collator("en", {
sensitivity: "base"
});
export const numericCollator = new Intl.Collator("en", {
numeric: true
});
// In your components
import { germanCollator } from "./utils/collation";
const sorted = names.sort(germanCollator.compare);
这种模式能最大限度提升性能,并在整个代码中保持一致的比较行为。
根据对象属性对数组排序
在比较函数中利用 collator 读取对象属性进行排序:
const collator = new Intl.Collator("de");
const users = [
{ name: "Zoe" },
{ name: "Änder" },
{ name: "Ava" }
];
const sorted = users.sort((a, b) =>
collator.compare(a.name, b.name)
);
这种方法适用于任何对象结构。提取要比较的字符串,并将它们传递给 collator。
Intl.Collator 与 localeCompare 性能对比
在对大型数据集排序时,Intl.Collator 能提供更好的性能:
// Slower: recreates locale settings for each comparison
const slow = items.sort((a, b) => a.localeCompare(b, "de"));
// Faster: reuses precomputed locale settings
const collator = new Intl.Collator("de");
const fast = items.sort(collator.compare);
对于小数组(少于 100 项),二者几乎没有区别。对于大型数组(成千上万项),Intl.Collator 的速度可以快 60-80%。
在基于 V8 的浏览器(如 Chrome)中有一个例外。localeCompare 针对纯 ASCII 字符串使用了查找表优化。在仅包含 ASCII 字符串的排序中,localeCompare 的性能可能与 Intl.Collator 相当。
何时使用 Intl.Collator 或 localeCompare
推荐使用 Intl.Collator 的情况:
- 排序大型数组(数百或数千项)
- 多次排序(如用户切换排序、虚拟列表)
- 构建可复用的比较工具
- 性能对你的场景很重要
推荐使用 localeCompare 的情况:
- 临时比较
- 排序小型数组(少于 100 项)
- 代码简单比性能更重要
- 需要直接在代码中进行比较,无需额外设置
这两个 API 支持相同的选项,并产生相同的结果。区别仅在性能和代码结构方面。
检查实际使用的选项
resolvedOptions 方法会返回 collator 实际生效的选项:
const collator = new Intl.Collator("de", { sensitivity: "base" });
console.log(collator.resolvedOptions());
// {
// locale: "de",
// usage: "sort",
// sensitivity: "base",
// ignorePunctuation: false,
// collation: "default",
// numeric: false,
// caseFirst: "false"
// }
这有助于调试排序行为并了解默认值。如果系统不支持完全一致的 locale,实际匹配到的 locale 可能会有所不同。
验证 locale 是否受支持
检查当前环境支持哪些 locale:
const supported = Intl.Collator.supportedLocalesOf(["de", "fr", "xx"]);
console.log(supported); // ["de", "fr"]
不受支持的 locale 会回退到系统默认值。此方法可以帮助检测请求的 locale 是否可用。
浏览器与环境支持
Intl.Collator 从 2017 年 9 月开始被广泛支持。所有现代浏览器和 Node.js 版本都已兼容,API 在各种环境中表现一致。
某些排序类型和选项在旧版浏览器中的支持可能有限。如果需要兼容旧环境,请务必测试关键功能或查阅 MDN 兼容性表。
常见错误须避免
不要为每次比较都新建一个 Collator 实例:
// Wrong: creates collator repeatedly
items.sort((a, b) => new Intl.Collator("de").compare(a, b));
// Right: create once, reuse
const collator = new Intl.Collator("de");
items.sort(collator.compare);
不要认为默认排序可以适用于多语言文本:
// Wrong: breaks for non-ASCII characters
names.sort();
// Right: use locale-aware sorting
names.sort(new Intl.Collator("de").compare);
进行搜索时不要忘记指定敏感度:
// Wrong: variant sensitivity requires exact match
const collator = new Intl.Collator("en");
items.filter(item => collator.compare(item, "apple") === 0);
// Right: base sensitivity for fuzzy matching
const collator = new Intl.Collator("en", { sensitivity: "base" });
items.filter(item => collator.compare(item, "apple") === 0);
实际应用场景
可在以下场景中使用 Intl.Collator:
- 排序用户生成内容(如姓名、标题、地址)
- 实现搜索和自动补全功能
- 构建带可排序列的数据表
- 创建筛选列表和下拉选项
- 排序文件名和版本号
- 在联系人列表中实现字母导航
- 多语言应用界面
任何向用户展示已排序文本的界面都可以从本地化排序中受益。这样能确保你的应用无论在哪种语言环境下都显得专业且准确。