大小写:为什么 toLowerCase 不够,正则 i 走的是另一套
大小写看着是最简单的一对(a↔A),却藏着三个陷阱:转换会改长度(ß 的大写是两个字母 SS)、会依赖 locale(土耳其语的 I 转小写不是 i)、而且「给人看的转换」(case mapping)和「给比较用的折叠」(case folding)是两套不同的规则。正则的 i 标志走的是后者。本页把这几层拆开,并对照
归一化那页——它和大小写折叠同属「比较前先把同义写法拍平」的家族。
四种操作:lowercase / uppercase / titlecase 映射,与 case folding
Unicode 把「变换大小写」分成映射(case mapping)和折叠(case folding)两件事,各有用途:
- case mapping(
toLowerCase/toUpperCase/ titlecase)—— 给人看的转换,结果要符合书写习惯,因此依赖 locale,也可能 1 对多(ß→SS)。titlecase 是「词首大写」专用形态,极少数字符的 title 形和 upper 形不同(dž的 title 是Dž、upper 是DŽ)。 - case folding —— 给机器比较用的规范形态,目标是「忽略大小写后能相等」。它不依赖 locale、结果稳定,不保证可读(
K开尔文符号U+212A折成普通k)。又分 simple(1 对 1)与 full(允许 1 对多,ß→ss)。
| 字符 | toLowerCase | toUpperCase | 说明 |
|---|---|---|---|
| ß U+00DF | ß | SS | 德语 eszett,upper 变两个字母 —— 大小写会改长度 |
| fi U+FB01 | fi | FI | 连字 fi,upper 拆成两个字母 |
| İ U+0130 | i̇ | İ | 带点大写 I,默认 lower 得 i+组合点(两码点) |
| Σ U+03A3 | σ / ς | Σ | 希腊 sigma,词尾小写是 ς、词中是 σ —— 上下文相关 |
| Dž U+01C5 | dž | DŽ | 本身就是 titlecase 形,upper / title / lower 三态都不同 |
| K U+212A | k | K | 开尔文符号,case fold 后等于普通 k(正则 i 因此能互相匹配) |
亲手试:大小写转换会改长度、改字形
输入一个字符,看 toLowerCase / toUpperCase 各把它变成什么,以及三层长度(UTF-16 码元 / code point / 字素簇)有没有变。full case mapping 的字符(ß fi st)转换后码点数会增加:
locale 相关:同一个 I,英文和土耳其结果不同
case mapping 不是全球统一的查表。最经典的是土耳其语 / 阿塞拜疆语:它们区分无点 i(i / I 之外另有 ı / İ)。同一个 I,英文规则转小写得 i,土耳其规则得无点的 ı —— 大小写不分语言地乱转,会把名字写错:
toLocaleLowerCase() 做这件事 —— 在土耳其语环境下 "I".toLocaleLowerCase() 得 ı,会让 "FILE" 与 "file" 比不相等。无关 locale 的规范化比较应该用 case folding(见下一节)。
正则 i 标志 = case folding,不是 toLowerCase
开了 i 的正则用 Unicode case folding 判等,这比「两边各 toLowerCase 再比」更严谨,能认出 K(开尔文 U+212A)= k、ſ(长 s U+017F)= s 这类。下面对每一对字符同时跑两条:/A/iu.test(B) 与 A.toLowerCase() === B.toLowerCase(),看它们何时给出不同答案:
每对:左字符当 pattern(加 iu)、右字符当待测串
i 用的是 simple case folding(1 对 1),所以它认 K=k、ſ=s,但不认 ß 与 ss(那是 full folding 的 1 对多)。这也是为什么 /straße/iu.test("STRASSE") 返回 false。本仓库
自研引擎那页的 i 同样走折叠等价表。
大小写相关的码点属性 \p{…}
UCD 里和大小写有关的二元属性,JS 正则都能直接查。输入一个字符,看它命中哪些(\p{Cased} 标记「有大小写之分」、\p{Case_Ignorable} 是大小写无关的附属符号、Changes_When_* 标记某种转换会不会改变它):
i 就是它)。两者都是「同义写法拍平」,和
归一化互补 —— 严谨的做法常常是先 normalize 再 fold。土耳其 i 这类还提醒:涉及具体语言的转换,务必显式带 locale。