regex 总览待审核
. 与 \s · 看不见的字符

空白与换行:. 不匹配什么、\s 匹配什么

syntax 页里 .\s 一笔带过。这页把它俩掰开看:JS 正则里 .(无 s 标志) 默认不匹配 4 个行终止符;而 \s 匹配的远不止一个空格——是 ECMAScript 的 WhiteSpace + LineTerminator 全集,涵盖 NBSP、各种排版空格、全角空格,甚至 BOM。这些都是「看不见却真实存在」的字符, 也是一大类难以定位的 bug 的源头。

. 与四个行终止符

「行终止符」在 JS 里恰好 4 个。. 默认跳过它们;开 s(dotAll)才让 . 吃下换行;它们也都属于 \s:

常被忘记的是 U+2028(LS)/ U+2029(PS)——它们也是行终止符,. 同样不匹配。 另外 m(multiline)改的是 ^ $ 的含义,不是 .;让 . 跨行的是 s,两者别混。

\s 的完整空白清单(按宽度对比)

下面每个都被 \s 命中(✓ 实时由 /^\s$/u 测出)。绿色标尺夹着空白本体,盒子宽度 = 这个空白真实的字形宽度——能直观看出 thin / en / em / 全角的宽窄差:

两个反直觉:其一 BOM U+FEFF\s 命中(它在 spec 里是 ZWNBSP,算 WhiteSpace)—— 所以一段开头带 BOM 的文本,\s 会把它当空白。其二 反过来,ZWSP U+200B(零宽空格)\s 命中—— 名字带 space,实则是格式字符(Cf),不在 WhiteSpace 里。U+200C(ZWNJ)/ U+200D(ZWJ)同理(见 Basic_Emoji 页)。

隐形字符扫描:把文本里的隐形字符标出来

从别处复制来的文本常混入 NBSP、全角空格、BOM——肉眼看着是普通空格,却让 ===trim()、数字解析全失灵。粘一段进来,每个空白/不可见字符都被标出来:

和自研引擎互证:引擎那页\s 用的就是上表这张码点表 (parser.ts 里的 SPACE),和 ECMAScript 逐项一致;. 默认排除的也正是这 4 个行终止符, 回溯与 Pike VM 两条引擎共用同一个 isLineTerminator 判定。实用建议:清洗输入时先 str.replace(/\s/gu, ' ') 把杂空白归一成普通空格,或按需 normalize + 去 BOM(replace(/^\uFEFF/, ''))。

\p{White_Space} 全量:25 个,按 gc 小分类摊开

上面那张表是 ECMAScript \s 的清单;Unicode 自己另有一个二元属性 White_Space(码点的档案页讲过)。两份名单高度重合却不相等。 这里把 White_Space=Yes 的全部 25 个码点按 General_Category 小分类摊开,每行实时 用 /^\s$/u\s 是否也认——一眼找出那个唯一的反例。

两份名单差在哪(恰好两处):其一 NEL U+0085White_Space=Yes,\s—— 就是上面 控制与换行 段里那个唯一打「·」的行,trim() 同样不剥它(EBCDIC 移植来的换行,JS 未纳入)。 其二 反过来,BOM U+FEFFWhite_Space=No,\s 反而认——所以它不在这张 25 个的表里, 却出现在上面的 \s 清单中。要严格按 Unicode 属性匹配(认 NEL、不认 BOM),用 /\p{White_Space}/u 而非 \s

看不见、却 White_Space=No:零宽与隐形字符全量速查

还有一大批字符肉眼根本看不见——零宽、bidi 方向控制、变体选择符、Tag、各脚本的隐形格式控制——但它们都 White_Space=No,于是 \s / \p{White_Space} / trim() 全都视而不见,它们就能在不被察觉的情况下混入文本:homoglyph 钓鱼、隐形水印、绕过「禁止空名」校验、Trojan Source。 这里把这类字符全量列出:已分配(gc≠Cn)且 \p{Default_Ignorable_Code_Point}\p{Cf}、 且 White_Space=No 的共 438 个(另纳入盲文空白 U+2800;排除了约 3769 个未分配的 DI 预留码位), 按用途分类、可搜索、可快速切换。「该被渲染器隐藏」的精确属性是 \p{Default_Ignorable_Code_Point}(DI 列,每行实时测);\s 列复看它们是否被 \s 误收。

几个要记的点:其一 DI 列大多打 ✓(该被渲染器隐藏);但有一批 gc=Cf「活动」格式符 DI=No—— 如 Arabic number sign U+0600(框住后续数字)、interlinear annotation U+FFF9–FFFB、Egyptian hieroglyph 格式控制—— 它们不是「隐藏自己」而是「影响排版」。盲文空白 U+2800DI=No,但属于伪空白(gc=So 真字形,画出来空), 和 HANGUL FILLER 一类是绕过「禁止空名」校验的主力。 其二 BOM U+FEFF 是全表唯一被 \s 收的(承前一节 \s 的历史遗留),其余 \s 一律不认。 其三 bidi override U+202E(RLO)把后文显示顺序反转 → 源码里藏反转就是 Trojan Source(详见 特殊用意的码点页)。其四 U+180E(蒙古文元音分隔)曾经是 White_Space(Zs), Unicode 6.3 起降级为 Cf——老代码若硬把它当空白,就和今天的 \p{White_Space} 对不上了。 结论:没有单一属性能一网打尽「看不见」,实务上多用 \p{Cf}\p{Default_Ignorable_Code_Point} 再补 U+2800 之类,或维护显式清单。