如何忽略大小写差异进行字符串比较
使用支持本地化的比较方法,确保在不同语言中正确处理不区分大小写的匹配
引言
在 Web 应用中,不区分大小写的字符串比较非常常见。用户在输入搜索内容时可能会混用大小写,填写用户名时大小写不一致,或在表单中忽略字母大小写。无论用户输入大写、小写还是混合大小写,您的应用都需要正确匹配这些输入。
最直接的方法是将两个字符串都转换为小写后再进行比较。这种方式适用于英文文本,但在国际化应用中会失效。不同语言在大小写转换上有不同的规则。适用于英语的比较方法,可能在土耳其语、德语、希腊语或其他语言中产生错误结果。
JavaScript 提供了 Intl.Collator API,可以在所有语言中正确处理不区分大小写的比较。本教程将解释为什么简单的小写转换会失败,本地化比较是如何工作的,以及在什么情况下应选择不同的方法。
使用 toLowerCase 的简单方法
在比较前将两个字符串都转换为小写,是实现不区分大小写匹配最常见的方法:
const str1 = "Hello";
const str2 = "HELLO";
console.log(str1.toLowerCase() === str2.toLowerCase());
// true
这种模式适用于 ASCII 文本和英文单词。比较时会将同一个字母的大写和小写视为相同。
您可以将这种方法用于模糊搜索:
const query = "apple";
const items = ["Apple", "Banana", "APPLE PIE", "Orange"];
const matches = items.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
console.log(matches);
// ["Apple", "APPLE PIE"]
该过滤器会找到所有包含搜索词的项目,无论大小写如何。这为用户在不考虑大小写时输入查询提供了预期的体验。
为什么简单方法在国际文本中会失败
toLowerCase() 方法会根据 Unicode 规则转换文本,但这些规则在不同语言中的表现并不一致。最著名的例子是土耳其语的 i 问题。
在英语中,小写字母 i 会转换为大写 I。而在土耳其语中,这两个字母是不同的:
- 小写带点
i会转换为大写带点İ - 小写无点
ı会转换为大写无点I
这种差异会导致不区分大小写的比较失败:
const word1 = "file";
const word2 = "FILE";
// In English locale (correct)
console.log(word1.toLowerCase() === word2.toLowerCase());
// true
// In Turkish locale (incorrect)
console.log(word1.toLocaleLowerCase("tr") === word2.toLocaleLowerCase("tr"));
// false - "file" becomes "fıle"
当使用土耳其语规则将 FILE 转换为小写时,I 会变为 ı(无点),生成 fıle。这与 file(带点 i)不一致,因此即使两个字符串表示同一个单词,比较结果也为 false。
其他语言也存在类似问题。德语有 ß 字符,其大写形式为 SS。希腊语中,西格玛有多个小写形式(σ 和 ς),但它们都转换为大写 Σ。简单的大小写转换无法正确处理这些特定语言的规则。
使用 Intl.Collator 进行基础敏感度的不区分大小写比较
Intl.Collator API 提供了支持区域设置的字符串比较,并可配置敏感度。sensitivity 选项用于控制比较时哪些差异会被考虑。
要进行不区分大小写的比较,请使用 sensitivity: "base":
const collator = new Intl.Collator("en", { sensitivity: "base" });
console.log(collator.compare("Hello", "hello"));
// 0 (strings are equal)
console.log(collator.compare("Hello", "HELLO"));
// 0 (strings are equal)
console.log(collator.compare("Hello", "Héllo"));
// 0 (strings are equal, accents ignored too)
基础敏感度会忽略大小写和重音符号的差异,仅比较基本字母。当字符串在此敏感度下等价时,比较结果为 0。
这种方法可以正确处理土耳其语 i 的问题:
const collator = new Intl.Collator("tr", { sensitivity: "base" });
console.log(collator.compare("file", "FILE"));
// 0 (correctly matches)
console.log(collator.compare("file", "FİLE"));
// 0 (correctly matches, even with dotted İ)
collator 会自动应用土耳其语的大小写折叠规则。无论输入中出现哪种大写 I,两个比较都能识别字符串等价。
使用 localeCompare 的 sensitivity 选项
localeCompare() 方法提供了一种区分大小写比较的替代方式。它接受与 Intl.Collator 相同的选项:
const str1 = "Hello";
const str2 = "HELLO";
console.log(str1.localeCompare(str2, "en", { sensitivity: "base" }));
// 0 (strings are equal)
这将产生与使用 Intl.Collator 并设置 base sensitivity 相同的结果。比较时会忽略大小写差异,对于等价字符串返回 0。
你可以在数组过滤中使用它:
const query = "apple";
const items = ["Apple", "Banana", "APPLE PIE", "Orange"];
const matches = items.filter(item =>
item.localeCompare(query, "en", { sensitivity: "base" }) === 0 ||
item.toLowerCase().includes(query.toLowerCase())
);
console.log(matches);
// ["Apple"]
但 localeCompare() 只会在指定 sensitivity 级别下完全匹配时返回 0。它不支持像 includes() 那样的部分匹配。要进行子串搜索,仍需使用小写转换或实现更复杂的搜索算法。
如何选择 base 和 accent sensitivity
sensitivity 选项接受四个值,用于控制字符串比较的不同方面:
Base sensitivity
Base sensitivity 会忽略大小写和重音符号:
const collator = new Intl.Collator("en", { sensitivity: "base" });
console.log(collator.compare("cafe", "café"));
// 0 (accents ignored)
console.log(collator.compare("cafe", "Café"));
// 0 (case and accents ignored)
console.log(collator.compare("cafe", "CAFÉ"));
// 0 (case and accents ignored)
这提供了最宽松的匹配方式。无法输入重音字符或为了方便省略重音的用户,依然可以获得正确的匹配结果。
Accent sensitivity
Accent sensitivity 忽略大小写,但区分重音符号:
const collator = new Intl.Collator("en", { sensitivity: "accent" });
console.log(collator.compare("cafe", "café"));
// -1 (accents matter)
console.log(collator.compare("cafe", "Café"));
// -1 (accents matter, case ignored)
console.log(collator.compare("Café", "CAFÉ"));
// 0 (case ignored, accents match)
这种方式会将带重音和不带重音的字母视为不同,但忽略大小写。当重音差异很重要而大小写不重要时,建议使用此选项。
针对不同场景选择合适的 sensitivity
对于大多数不区分大小写的比较需求,base sensitivity 能带来最佳用户体验:
- 搜索功能,用户输入时不带重音符号
- 用户名匹配,不区分大小写
- 模糊查找,需最大灵活性
- 表单校验,
Smith和smith应视为匹配
在以下情况下应使用重音敏感性:
- 语言需要区分带重音和不带重音的字符
- 数据中同时包含带重音和不带重音的版本且含义不同
- 需要大小写不敏感但区分重音的比较
使用 includes 进行不区分大小写的搜索
Intl.Collator API 比较完整字符串,但不支持子串匹配。要实现不区分大小写的搜索,仍需将本地化比较与其他方法结合使用。
一种选择是使用 toLowerCase() 进行子串搜索,但需接受其在国际文本处理上的局限性:
function caseInsensitiveIncludes(text, query, locale = "en") {
return text.toLowerCase().includes(query.toLowerCase());
}
const text = "The Quick Brown Fox";
console.log(caseInsensitiveIncludes(text, "quick"));
// true
如需更复杂且能正确处理国际文本的搜索,需要遍历所有可能的子串位置,并对每个位置使用 collator 进行比较:
function localeAwareIncludes(text, query, locale = "en") {
const collator = new Intl.Collator(locale, { sensitivity: "base" });
for (let i = 0; i <= text.length - query.length; i++) {
const substring = text.slice(i, i + query.length);
if (collator.compare(substring, query) === 0) {
return true;
}
}
return false;
}
const text = "The Quick Brown Fox";
console.log(localeAwareIncludes(text, "quick"));
// true
该方法会检查所有长度合适的子串,并对每个子串使用本地化比较。它能正确处理国际文本,但性能不如简单的 includes()。
使用 Intl.Collator 时的性能考量
创建 Intl.Collator 实例时需要加载本地化数据并处理选项。如果需要多次比较,建议只创建一次 collator 并复用:
// Inefficient: creates collator for every comparison
function badCompare(items, target) {
return items.filter(item =>
new Intl.Collator("en", { sensitivity: "base" }).compare(item, target) === 0
);
}
// Efficient: creates collator once, reuses it
function goodCompare(items, target) {
const collator = new Intl.Collator("en", { sensitivity: "base" });
return items.filter(item =>
collator.compare(item, target) === 0
);
}
高效的做法是在过滤前只创建一次 collator。每次比较都复用同一个实例,避免重复初始化带来的开销。
对于需要频繁比较的应用,建议在应用启动时创建 collator 实例,并导出以便在整个代码库中使用:
// utils/collation.js
export const caseInsensitiveCollator = new Intl.Collator("en", {
sensitivity: "base"
});
export const accentInsensitiveCollator = new Intl.Collator("en", {
sensitivity: "accent"
});
// In your application code
import { caseInsensitiveCollator } from "./utils/collation";
const isMatch = caseInsensitiveCollator.compare(input, expected) === 0;
这种模式可最大化性能,并在整个应用中保持一致的比较行为。
何时使用 toLowerCase 与 Intl.Collator
对于仅支持英文且文本内容可控、只包含 ASCII 字符的应用,toLowerCase() 可以获得可接受的结果:
// Acceptable for English-only, ASCII-only text
const isMatch = str1.toLowerCase() === str2.toLowerCase();
这种方法简单、快速,并且大多数开发者都很熟悉。如果你的应用确实从不处理国际化文本,那么引入支持本地化的比较所带来的复杂性可能并没有实际价值。
对于国际化应用,或者用户可能输入任意语言文本的应用,应使用 Intl.Collator 并设置合适的 sensitivity 参数:
// Required for international text
const collator = new Intl.Collator(userLocale, { sensitivity: "base" });
const isMatch = collator.compare(str1, str2) === 0;
这样可以确保无论用户使用哪种语言输入或书写,都能获得正确的行为。使用 Intl.Collator 带来的轻微性能开销是值得的,可以避免错误的比较结果。
即使你的应用目前只支持 English,从一开始就采用本地化比较也能让未来的国际化工作更加轻松。添加新语言支持时,无需更改比较逻辑。
不区分大小写比较的实际用例
不区分大小写的比较在许多常见场景中都会用到:
用户名和邮箱匹配
用户输入用户名和邮箱地址时,大小写经常不一致:
const collator = new Intl.Collator("en", { sensitivity: "base" });
function findUserByEmail(users, email) {
return users.find(user =>
collator.compare(user.email, email) === 0
);
}
const users = [
{ email: "[email protected]", name: "John" },
{ email: "[email protected]", name: "Jane" }
];
console.log(findUserByEmail(users, "[email protected]"));
// { email: "[email protected]", name: "John" }
这样可以无论用户如何输入邮箱地址的大小写,都能找到对应的用户。
搜索自动补全
自动补全建议需要以不区分大小写的方式匹配部分输入:
const collator = new Intl.Collator("en", { sensitivity: "base" });
function getSuggestions(items, query) {
const queryLower = query.toLowerCase();
return items.filter(item =>
item.toLowerCase().startsWith(queryLower)
);
}
const items = ["Apple", "Apricot", "Banana", "Cherry"];
console.log(getSuggestions(items, "ap"));
// ["Apple", "Apricot"]
这样可以无论用户输入的大小写如何,都能提供相应的建议。
标签和分类匹配
用户为内容分配标签或分类时,大小写通常不统一:
const collator = new Intl.Collator("en", { sensitivity: "base" });
function hasTag(item, tag) {
return item.tags.some(itemTag =>
collator.compare(itemTag, tag) === 0
);
}
const article = {
title: "My Article",
tags: ["JavaScript", "Tutorial", "Web Development"]
};
console.log(hasTag(article, "javascript"));
// true
这样可以无论大小写差异如何,都能正确匹配标签。