比较字符串时忽略重音符号
了解如何使用 JavaScript 的标准化和 Intl.Collator 来比较字符串时忽略变音符号
介绍
在构建支持多语言的应用程序时,您经常需要比较包含重音符号的字符串。例如,用户搜索 "cafe" 时,应该找到 "café" 的结果。检查用户名 "Jose" 时,应该匹配 "José"。标准的字符串比较会将它们视为不同的字符串,但您的应用程序逻辑需要将它们视为相同。
JavaScript 提供了两种方法来解决这个问题。您可以对字符串进行规范化并移除重音符号,或者使用内置的排序 API 根据特定的敏感度规则比较字符串。
什么是重音符号
重音符号是放置在字母上方、下方或穿过字母的符号,用于修改其发音或含义。这些符号被称为变音符号。常见的例子包括 "é" 中的尖音符、"ñ" 中的波浪号以及 "ü" 中的分音符。
在 Unicode 中,这些字符可以通过两种方式表示。一个单一的代码点可以表示完整的字符,或者多个代码点可以将基本字母与单独的重音符号组合起来。字母 "é" 可以存储为 U+00E9,或者存储为 "e" (U+0065) 加上一个组合尖音符 (U+0301)。
何时在比较中忽略重音符号
搜索功能是最常见的需要忽略重音符号的比较用例。用户在输入查询时不带重音符号,期望找到包含重音字符的内容。例如,搜索 "Muller" 应该找到 "Müller"。
用户输入验证在检查用户名、电子邮件地址或其他标识符是否已存在时需要这种功能。您需要防止 "maria" 和 "maría" 创建重复账户。
不区分大小写的比较通常也需要同时忽略重音符号。当检查两个字符串是否匹配而不考虑大小写时,通常也希望忽略重音差异。
使用规范化去除重音符号
第一种方法将字符串转换为规范化形式,将基本字母和重音符号分离,然后去除重音符号。
Unicode 规范化将字符串转换为标准形式。NFD(规范分解)形式将组合字符分解为基本字母和组合标记。字符串 "café" 会变成 "cafe",后跟一个组合的急音符号字符。
规范化后,可以使用正则表达式去除组合标记。Unicode 范围 U+0300 到 U+036F 包含组合变音符号。
function removeAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
const text1 = 'café';
const text2 = 'cafe';
const normalized1 = removeAccents(text1);
const normalized2 = removeAccents(text2);
console.log(normalized1 === normalized2); // true
console.log(normalized1); // "cafe"
此方法可以生成没有重音符号的字符串,您可以使用标准的相等运算符进行比较。
您可以将此方法与小写转换结合使用,以实现不区分大小写和重音符号的比较。
function normalizeForComparison(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
}
const search = 'muller';
const name = 'Müller';
console.log(normalizeForComparison(search) === normalizeForComparison(name)); // true
当您需要存储或索引字符串的规范化版本以进行高效搜索时,此方法非常有效。
使用 Intl.Collator 比较字符串
第二种方法使用 Intl.Collator API,它提供了支持区域设置的字符串比较功能,并具有可配置的敏感度级别。
Intl.Collator 对象根据语言特定的规则比较字符串。敏感度选项控制比较字符串时哪些差异是重要的。
"base" 敏感度级别忽略重音符号和大小写差异。仅在重音符号或大小写不同的字符串被视为相等。
const collator = new Intl.Collator('en', { sensitivity: 'base' });
console.log(collator.compare('café', 'cafe')); // 0(相等)
console.log(collator.compare('Café', 'cafe')); // 0(相等)
console.log(collator.compare('café', 'caff')); // -1(第一个在第二个之前)
compare 方法在字符串相等时返回 0,第一个字符串在第二个之前时返回负数,第一个字符串在第二个之后时返回正数。
您可以将此方法用于相等性检查或数组排序。
const collator = new Intl.Collator('en', { sensitivity: 'base' });
function areEqualIgnoringAccents(str1, str2) {
return collator.compare(str1, str2) === 0;
}
console.log(areEqualIgnoringAccents('José', 'Jose')); // true
console.log(areEqualIgnoringAccents('naïve', 'naive')); // true
对于排序,您可以将 compare 方法直接传递给 Array.sort。
const names = ['Müller', 'Martinez', 'Muller', 'Márquez'];
const collator = new Intl.Collator('en', { sensitivity: 'base' });
names.sort(collator.compare);
console.log(names); // 将变体分组在一起
Intl.Collator API 提供了其他敏感度级别以适应不同的用例。
"accent" 级别忽略大小写但区分重音符号差异。"Café" 等于 "café",但不等于 "cafe"。
const accentCollator = new Intl.Collator('en', { sensitivity: 'accent' });
console.log(accentCollator.compare('Café', 'café')); // 0(相等)
console.log(accentCollator.compare('café', 'cafe')); // 1(不相等)
"case" 级别忽略重音符号但区分大小写差异。"café" 等于 "cafe",但不等于 "Café"。
const caseCollator = new Intl.Collator('en', { sensitivity: 'case' });
console.log(caseCollator.compare('café', 'cafe')); // 0(相等)
console.log(caseCollator.compare('café', 'Café')); // -1(不相等)
"variant" 级别区分所有差异。这是默认行为。
const variantCollator = new Intl.Collator('en', { sensitivity: 'variant' });
console.log(variantCollator.compare('café', 'cafe')); // 1(不相等)
在标准化和排序之间进行选择
这两种方法都能在忽略重音的比较中产生正确的结果,但它们具有不同的特性。
标准化方法会创建没有重音标记的新字符串。当您需要存储或索引标准化版本时,请使用此方法。搜索引擎和数据库通常存储标准化文本以实现高效查找。
Intl.Collator 方法在不修改字符串的情况下进行比较。当您需要直接比较字符串时,例如检查重复项或对列表进行排序时,请使用此方法。排序器遵循语言特定的排序规则,而简单的字符串比较无法处理这些规则。
性能考虑因使用场景而异。创建一个排序器对象并多次重用它对于多次比较是高效的。而当您只需标准化一次并多次比较时,标准化字符串是高效的。
标准化方法会永久移除重音信息。而排序方法在比较时根据您指定的规则保留原始字符串。
使用忽略重音的搜索过滤数组
一个常见的用例是基于用户输入过滤一个项目数组,同时忽略重音差异。
const products = [
{ name: 'Café Latte', price: 4.50 },
{ name: 'Crème Brûlée', price: 6.00 },
{ name: 'Croissant', price: 3.00 },
{ name: 'Café Mocha', price: 5.00 }
];
function searchProducts(query) {
const collator = new Intl.Collator('en', { sensitivity: 'base' });
return products.filter(product => {
return collator.compare(product.name.slice(0, query.length), query) === 0;
});
}
console.log(searchProducts('cafe'));
// 返回 Café Latte 和 Café Mocha
对于子字符串匹配,标准化方法效果更好。
function removeAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
function searchProducts(query) {
const normalizedQuery = removeAccents(query.toLowerCase());
return products.filter(product => {
const normalizedName = removeAccents(product.name.toLowerCase());
return normalizedName.includes(normalizedQuery);
});
}
console.log(searchProducts('creme'));
// 返回 Crème Brûlée
此方法检查标准化后的产品名称是否包含标准化后的搜索查询作为子字符串。
处理文本输入匹配
在根据现有数据验证用户输入时,您需要进行不区分重音的比较,以防止混淆和重复。
const existingUsernames = ['José', 'María', 'François'];
function isUsernameTaken(username) {
const collator = new Intl.Collator('en', { sensitivity: 'base' });
return existingUsernames.some(existing =>
collator.compare(existing, username) === 0
);
}
console.log(isUsernameTaken('jose')); // true
console.log(isUsernameTaken('Maria')); // true
console.log(isUsernameTaken('francois')); // true
console.log(isUsernameTaken('pierre')); // false
这可以防止用户创建仅在重音或大小写上与现有账户不同的账户。
浏览器和环境支持
String.prototype.normalize 方法在所有现代浏览器和 Node.js 环境中都受支持。Internet Explorer 不支持此方法。
Intl.Collator API 在所有现代浏览器和 Node.js 版本中都受支持。Internet Explorer 11 提供部分支持。
这两种方法在当前的 JavaScript 环境中都能可靠地工作。如果需要支持旧版浏览器,则需要使用 polyfill 或替代实现。
去除重音的局限性
某些语言使用变音符号来创建不同的字母,而不仅仅是重音变化。例如,在土耳其语中,"i" 和 "ı" 是不同的字母。在德语中,"ö" 是一个独立的元音,而不是带重音的 "o"。
在这些情况下,去除重音会改变含义。请考虑不区分重音的比较是否适合您的用例和目标语言。
排序方法在这些情况下表现更好,因为它遵循特定语言的规则。在 Intl.Collator 构造函数中指定正确的语言环境可以确保符合文化习惯的比较。
const turkishCollator = new Intl.Collator('tr', { sensitivity: 'base' });
const germanCollator = new Intl.Collator('de', { sensitivity: 'base' });
在选择比较策略时,请始终考虑您的应用程序支持的语言。