正则基础与字符类
正则表达式有哪两种创建方式?
- 组成: 正则 =
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