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 附近,而法语对带重音的字符有不同的处理方式。二进制比较忽略了这些文化惯例。
字符串排序的工作原理
排序是根据语言特定规则比较和排列字符串的过程。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" 之前。如果没有该选项,"10" 会排在 "2" 之前,因为 "1" 排在 "2" 之前。
重复调用 localeCompare 的性能问题
每次调用 localeCompare 都会从头处理区域设置。当对大型数组进行排序时,这会产生显著的开销:
// 效率低:每次比较都会处理区域设置
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 选项进行排序和搜索模式
使用选项优化了排序或搜索的比较器行为:
// 为排序优化
const sortCollator = new Intl.Collator("en", { usage: "sort" });
// 为搜索优化
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 选项决定大写字母或小写字母优先排序:
// 大写优先
const upperFirst = new Intl.Collator("en", { caseFirst: "upper" });
console.log(["a", "A", "b", "B"].sort(upperFirst.compare));
// ["A", "a", "B", "b"]
// 小写优先
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。当标点符号不应影响字符串排序或匹配时,请使用此选项。
为语言特定规则指定排序类型
某些语言环境支持多种排序类型以实现特定的排序需求:
// 中文拼音排序
const pinyin = new Intl.Collator("zh-CN-u-co-pinyin");
// 德语电话簿排序
const phonebook = new Intl.Collator("de-DE-u-co-phonebk");
// 表情符号分组
const emoji = new Intl.Collator("en-u-co-emoji");
排序类型通过语言环境字符串中的 Unicode 扩展语法指定。常见的类型包括:
pinyin:按罗马化发音对中文进行排序stroke:按笔画数对中文进行排序phonebk:德语电话簿排序trad:某些语言的传统排序规则emoji:按类别分组表情符号
请检查 Intl.supportedValuesOf 以了解您的环境中可用的排序类型。
在应用程序中重用排序器实例
创建排序器实例一次,并在整个应用程序中重用它们:
// 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
});
// 在您的组件中
import { germanCollator } from "./utils/collation";
const sorted = names.sort(germanCollator.compare);
这种模式最大化了性能,并在代码库中保持一致的比较行为。
按属性对对象数组进行排序
在访问对象属性的比较函数中使用排序器:
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)
);
这种方法适用于任何对象结构。提取要比较的字符串并将它们传递给排序器。
比较 Intl.Collator 和 localeCompare 的性能
在对大型数据集进行排序时,Intl.Collator 提供了更好的性能:
// 较慢:每次比较都会重新创建语言环境设置
const slow = items.sort((a, b) => a.localeCompare(b, "de"));
// 较快:重用预先计算的语言环境设置
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 方法返回排序器实际使用的选项:
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"
// }
这有助于调试排序行为并了解默认值。如果系统不支持请求的确切区域设置,解析的区域设置可能会有所不同。
验证区域设置支持
检查当前环境中支持哪些区域设置:
const supported = Intl.Collator.supportedLocalesOf(["de", "fr", "xx"]);
console.log(supported); // ["de", "fr"]
不支持的区域设置会回退到系统默认值。此方法有助于检测请求的区域设置不可用的情况。
浏览器和环境支持
自 2017 年 9 月以来,Intl.Collator 已被广泛支持。所有现代浏览器和 Node.js 版本都支持它。该 API 在不同环境中工作一致。
某些排序类型和选项在旧版浏览器中可能支持有限。如果需要支持旧环境,请测试关键功能或查看 MDN 兼容性表。
避免常见错误
不要为每次比较创建一个新的 Collator:
// 错误:每次都创建新的 Collator
items.sort((a, b) => new Intl.Collator("de").compare(a, b));
// 正确:创建一次,重复使用
const collator = new Intl.Collator("de");
items.sort(collator.compare);
不要假设默认排序适用于国际化文本:
// 错误:对非 ASCII 字符排序会出错
names.sort();
// 正确:使用支持区域设置的排序
names.sort(new Intl.Collator("de").compare);
不要忘记为搜索指定敏感度:
// 错误:变体敏感度需要完全匹配
const collator = new Intl.Collator("en");
items.filter(item => collator.compare(item, "apple") === 0);
// 正确:基础敏感度用于模糊匹配
const collator = new Intl.Collator("en", { sensitivity: "base" });
items.filter(item => collator.compare(item, "apple") === 0);
实际使用场景
使用 Intl.Collator 进行:
- 排序用户生成的内容(如姓名、标题、地址)
- 实现搜索和自动完成功能
- 构建带有可排序列的数据表
- 创建过滤列表和下拉选项
- 排序文件名和版本号
- 通讯录中的字母导航
- 多语言应用界面
任何向用户显示排序文本的界面都可以从支持区域设置的排序中受益。这确保了您的应用程序无论用户使用何种语言都显得本地化且正确。