跳到主要内容

正则基础与字符类

正则表达式有哪两种创建方式?​

  • 组成: 正则 = pattern (模式) + 可选 flags (标志);
  • 字面量: /pattern/flags, 斜杠等价于字符串的引号, 书写时模式已确定;
  • 构造函数: new RegExp(pattern, flags), 模式是字符串, 可运行时拼接;
  • 共同点: 两者都得到 RegExp 实例, 匹配能力等价;
  • 取舍: 字面量不能插值, 构造函数可从字符串动态生成模式 (如用户输入);
const tag = "h2";
const re1 = new RegExp(`<${tag}>`); // 动态
const re2 = /<h2>/; // 等价静态写法

flag 分别控制什么?​

  • 性质: flag 是整体开关, 不改模式本身, 只改引擎的匹配策略;
  • i: 忽略大小写;
  • g: 返回全部匹配, 否则只返回第一个;
  • m: 多行模式, 让 ^/$ 按行匹配, 详见 锚点与边界;
  • s: dotall, 让 . 也匹配换行符;
  • u: 开启完整 Unicode 支持, 允许 \p{...};
  • y: 粘性, 只从 lastIndex 处开始匹配;
  • d: 结果中带上匹配的起止索引;
  • 逐项细节、实例属性与 exec/test: 见 Date 与 RegExp;
  • str.match/replace/search 等字符串侧入口: 见 String;

str.match 与 str.replace 有哪些行为差异?​

  • match: 无 g 返回首个匹配的数组, 含 index 与 input; 有 g 返回全部匹配的字符串数组;
  • 无匹配: 一律返回 null 而非空数组, 直接取 .length 会报错, 可用 match(...) || [] 兜底;
  • replace: 默认只替换首个匹配, 有 g 才全部替换; 替换串记号 $& 整个匹配、$` 匹配前文本、$' 匹配后文本、$$ 字面 $;
  • 分组引用 $n/$<name> 与 exec/test: 见 Date 与 RegExp;
"We will, we will".replace(/we/gi, "I"); // I will, I will
"I love HTML".replace(/HTML/, "$& and JavaScript"); // I love HTML and JavaScript
"JavaScript".match(/HTML/); // null

.、\d、\s、\w 各匹配什么?​

  • 字符类: 一个匹配"某些字符中任意一个"的记号, 每个类正好消费一个字符;
类含义
\d数字 0-9
\s空白字符: 空格、\t、\n、\v、\f、\r 及少数其它 Unicode 空白
\w"单词字符": 拉丁字母、数字、下划线 _ (不含西里尔、汉字等)
.任意字符, 默认不含换行 \n
  • 反向类: 同名大写字母取补集, 即 \D、\S、\W; 普通字符可与类拼写在同一模式中, CSS\d 匹配 CSS4;
"+7(903)-123-45-67".match(/\d/g).join(""); // 79031234567
"+7(903)-123-45-67".replace(/\D/g, ""); // 79031234567
"Is there CSS4?".match(/CSS\d/); // CSS4
  • . 是"任意一个字符"而非"没有字符": "CS4".match(/CS.4/) 返回 null;
  • s 标志: 让 . 匹配包括 \n 在内的任意字符; 无 s 时可用 [\s\S] (空格或非空格, 恒为真) 达到同样效果;
  • 空格: 是普通字符, 与其它字符同等重要, 不能随意增删;
"A\nB".match(/A.B/); // null
"A\nB".match(/A.B/s); // 匹配
"A\nB".match(/A[\s\S]B/); // 匹配
"1 - 5".match(/\d-\d/); // null
"1 - 5".match(/\d\s-\s\d/); // 1 - 5

字符集合与范围如何书写?​

  • 集合 [...]: 匹配其中任意一个字符, 整个集合只消费一个字符;
  • 范围: [a-z]、[0-9]、[0-9A-F], 多个范围可并排书写;
  • 类入集合: [\w-]、[\s\d] 等, 表示"类中字符或另一类中字符";
  • 简写来源: \d = [0-9], \w = [a-zA-Z0-9_], \s = [\t\n\v\f\r ] 加少数 Unicode 空白;
  • 排除集合 [^...]: 匹配不在给定集合中的一个字符, [^0-9] 等价 \D, [^\s] 等价 \S;
"Mop top".match(/[tm]op/gi); // Mop, top
"Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g); // xAF
"Voila".match(/V[oi]la/); // null: 集合只占一个字符
"alice15@gmail.com".match(/[^\d\sA-Z]/gi); // @, .
  • 集合内转义: .、+、(、) 无需转义, 按字面理解;
  • 连字符 -: 在开头或结尾不表示范围, 无需转义;
  • 脱字符 ^: 只在开头作为排除符号时才需转义;
  • 右方括号 ]: 必须转义;
  • 多转义无害: [\-\(\)\.\^\+] 与 [-().^+] 等价;

特殊字符如何转义?​

  • 元字符: [ ] { } ( ) \ ^ $ . | ? * + 在正则中有特殊含义;
  • 转义: 前置反斜杠, 如 \. 表示真正的点; 反斜杠自身写成 \\;
  • 斜杠 /: 本身不特殊, 但在 /.../ 字面量中是定界符, 需写成 \/; 用 new RegExp 时不必转义;
  • new RegExp 的坑: 字符串先"吃掉"一层反斜杠, "\d\.\d" 实为 d.d, 需双写为 "\\d\\.\\d";
"Chapter 5.1".match(/\d\.\d/); // 5.1
"Chapter 511".match(/\d\.\d/); // null
"function g()".match(/g\(\)/); // g()
"1\\2".match(/\\/); // \
"/".match(/\//); // /
new RegExp("\\d\\.\\d").test("Chapter 5.1"); // true

u 标志与 \p{...} 解决什么问题?​

  • 背景: JS 字符串用 UTF-16, 基本平面外字符占 4 字节即代理对, length 数成 2; 正则默认也把它当两个 2 字节字符, 于是出现半个字符、非法范围等错误;
  • u 作用: 4 字节字符按单个字符处理; 并解锁 Unicode 属性搜索 \p{...} (无 u 时 \p 不生效);
"𝒳".match(/[𝒳𝒴]/); // 只匹配到半个字符
"𝒳".match(/[𝒳𝒴]/u); // 𝒳
"𝒳".match(/[��-��]/); // SyntaxError: 半码点构成非法范围
"A ბ ㄱ".match(/\p{L}/gu); // A, ბ, ㄱ
"A ბ ㄱ".match(/\p{L}/g); // null
  • 主类别: L 字母、N 数字、P 标点、M 标记、S 符号、Z 分隔符、C 其它;
  • 子类与派生示例: Ll 小写字母、Nd 十进制数字、Pc 连接标点、Alpha、Hex_Digit;
  • 书写系统: \p{sc=Han} 汉字、\p{sc=Cyrillic} 西里尔文;
"number: xAF".match(/x\p{Hex_Digit}\p{Hex_Digit}/u); // xAF
"Prices: $2, €1, ¥9".match(/\p{Sc}\d/gu); // $2, €1, ¥9
  • 跨语言 \w: \w 只认拉丁字母, 通用写法是 [\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}], 依次为各语言字母、重音标记、数字、连接标点、连字控制符 200c/200d;
  • 兼容性: IE 不支持 \p{...}, 可退回 [\s\S] 或手写字符范围 (如 [а-я]);
"Hi 你好 12".match(/[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/gu);
// H, i, 你, 好, 1, 2