RegExp
"正则表达式,让复杂的匹配变得简单" —— Steve Jobs。
正则表达式是一种通用的字符串匹配和处理语言,不属于任何特定的编程语言或操作系统,因此没有单独的官方网站。不过,各个编程语言的官方文档和教程中都会包含关于该语言中正则表达式的相关内容。本章讲的是 JavaScript 中的正则,MDN 上就有 JavaScript 正则的相关文档(可参考:MDN 正则表达式)。
B 站上向军大叔的视频还不错,简短舒适,适合入门。
资料:
《JavaScript 正则迷你书》 简单易懂,我阅读的第一本正则表达式书籍。
以下是 《JavaScript 正则迷你书》 作者推荐的参考资料:
《JavaScript 高级程序设计》第四版,5.2 节,比较简短的介绍。
《JavaScript 忍者秘籍》 第 7 章,大概讲了一下正则的用法,几个例子还不错。
《JavaScript 权威指南》 第七版,第 11 章,🛵推荐!!描写详细,建议入门再看。
《高性能 JavaScript》第 5 章,讲了回溯和优化。🛵正则进阶必看!!
《精通正则表达式》第三版 ,权威且比较杂乱,没有讲 JavaScript 正则,有兴趣可看。
《正则表达式经典实例》 ,以实例为主导的一本书。
《正则表达式必知必会》。《正则指引》。
《Introducing Regular Expressions》
下边是看完部分参考资料后摘录的一些重点,并汇总了速查表并加以说明。
Note
JavaScript 正则迷你书
正则表达式的匹配,要么匹配字符,要么匹配位置。
- 匹配字符,
- 横向模糊(使用量词、+、*、?),一个正则可匹配的字符串的长度不是固定的,可以是多种情况的。
- 纵向模糊(使用字符组、多选分支),一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能。
- 匹配位置(使用 ^ 、$ 、\b 、\B 、(?=p) 、(?!p)),位置(锚)是相邻字符之间的位置(可看下列图片)。
JavaScript 权威指南
字面量
| 字面量 | |
|---|---|
| 字符 | 说明 |
| \0 | 匹配 NUL 字符。(\u0000) |
| \t | 匹配水平制表符。(\u0009) |
| \v | 匹配垂直制表符。(\u000B) |
| \n | 匹配换行符。(\u000A) |
| \r | 匹配回车符。(\u000D) |
| \f | 匹配换页符。(\u000C) |
| \x00:\xFF | 十六进制 ASCII 码(拉丁字符 Latin)。例如,\x0A 等价于 \n。 |
| \u0000:\uFFFF | 十六进制 Unicode 码。例如,\u0009 等价于 \t |
| \cA:\cZ | 控制字符,例如 \cJ 等价于 \n |
元字符
| 元字符 | |
|---|---|
| 字符 | 说明 |
| . | 除了 \u2028 行终止符、\u2029 段终止符、\r、\n 这些换行符外,的任意字符 |
| ^ | 脱字符,匹配行首。在多行模式中,也可以匹配每一行的行首 |
| $ | 匹配行尾。在多行模式中,也可以匹配每一行的行尾 |
| * | 匹配前面的字符0次或多次 |
| + | 匹配前面的字符1次或多次 |
| ? | 匹配前面的字符0次或1次。在量词后面加上问好,可以将贪婪量词变为非贪婪量词 |
| {n} | 匹配前面的字符恰好n次 |
| {n,} | 匹配前面的字符至少n次 |
| {n,m} | 匹配前面的字符至少n次,但不超过m次 |
| [] | 字符组,匹配其中任意一个字符 |
| [^] | 否定字符组,匹配除了其中字符的任意字符 |
| () | 捕获组,用于分组匹配。匹配到的内容可以在后续表达式中使用 |
| \w | 等同于[a-zA-Z0-9_] |
| \W | 和\w相反,等同于[^a-zA-Z0-9_] |
| \s | 任何Unicode空白符,等同于 [ \t\v\n\r\f],注意包含了空格 |
| \S | 和\s相反,等价于 [^ \t\v\n\r\f] |
| \d | 等价于[0-9] |
| \D | 和\d相反,等价于[^0-9] |
| [\b] | 退格(特例) |
| \b | 匹配单词边界,即单词和非单词字符之间的位置(具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。) |
| \B | 匹配非单词边界 |
锚字符
| 字符 | 说明 |
|---|---|
| ^ | 脱字符,匹配行首。在多行模式中,也可以匹配每一行的行首 |
| $ | 匹配行尾。在多行模式中,也可以匹配每一行的行尾 |
| \b | 匹配单词边界,即单词和非单词字符之间的位置(具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置。) |
| \B | 匹配非单词边界 |
| (?=p) | 零度正向先行断言,要求接下来的字符都与 p 匹配,但不能包括匹配p的那些字符 |
| (?!p) | 零宽负向先行断言,要求接下来的字符不与 p 匹配 |
| (?<=p) | 零度正向后行断言,要求前面的字符都与 p 匹配,但不能包括匹配p的那些字符 |
| (?<!p) | 零宽负向后行断言,要求前面的字符不与 p 匹配 |
JavaScript 高级程序设计(第 4 版)
标志(flags)
| 标志(flags) | |
|---|---|
| 字符 | 说明 |
| g | 全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。 |
| i | 不区分大小写,表示在查找匹配时忽略正则和字符串的大小写 |
| m | 多行模式,表示字符串有换行时,每行文本都当成一个整体去匹配 |
| y | 粘附模式,表示只查找第一个匹配的内容就结束。 |
| u | Unicode 模式,没有 u 修饰符的情况下,正则表达式引擎默认将输入字符串视为字节序列,而不是字符序列。这意味着,对于一个多字节的 Unicode 字符,如果在正则表达式中使用了量词,例如 `{n}`,则匹配的是 n 个字节,而不是 n 个字符。使用 u 修饰符,元字符 "." 将匹配任何 Unicode 字符,而不仅仅是 ASCII 字符,而元字符 `\w` 和 `\b` 将会考虑 Unicode 字符的属性来匹配单词字符和单词边界。在 RegExp 上设置 u 标志还允许你使用新的 \u{…} 转义序列用于 Unicode 字符,并且还启用 \p{…} 符号用于 Unicode 字符类。 |
| s | dotAll 模式,表示元字符 "." 匹配任何字符(就是换行符也能匹配了)。 |
示例
m 标志
var result = "I\nlove\njavascript".replace(/^|$/g, '#');
console.log(result);
/*
#I
love
javascript#
*/
// ---------------- cut ------------------------
var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
console.log(result);
/*
#I#
#love#
#javascript#
*/
// ===或===
var str = `I
love
javascript`;
var result = str.replace(/^|$/gm, '#');
console.log(result);
/*
#I#
# love#
# javascript#
*/
y 标志
正则如果有 y 标志:
调用 exec 方法会先返回一个数组,包含第一个和正则匹配的字符串的信息。 (更新 lastIndex)
继续调用 exec 方法,那么后续字符串就不匹配了,直接返回 null。(lastIndex 为 0)
如果继续调用 exec 方法,则会重新从第一步骤开始,如何往复。
let text = "cat, bat, sss, fff";
let pattern = /.at/y;
console.log(pattern.exec(text));
// [0: "cat", index: 0, input: "cat, bat, sss, fff", groups: undefined]
console.log(pattern.lastIndex); // 3
console.log(pattern.exec(text)); // null
console.log(pattern.lastIndex); // 0
console.log(pattern.exec(text));
// [0: "cat", index: 0, input: "cat, bat, sss, fff", groups: undefined]
console.log(pattern.lastIndex); // 3
正则如果没有标志:
exec 方法永远返回一个数组,包含第一个和正则匹配的字符串的信息。(lastIndex 始终为 0)
let text = "cat, bat, sss, fff";
let pattern = /.at/;
console.log(pattern.exec(text));
// [0: "cat", index: 0, input: "cat, bat, sss, fff", groups: undefined]
console.log(pattern.lastIndex); // 0
console.log(pattern.exec(text));
// [0: "cat", index: 0, input: "cat, bat, sss, fff", groups: undefined]
console.log(pattern.lastIndex); // 0
正则如果有 g 标志:
- 调用 exec 方法会先返回一个数组,包含第一个和正则匹配的字符串的信息。 (更新 lastIndex)
- 继续调用 exec 方法,会先返回一个数组,包含第二个和正则匹配的字符串的信息。 (更新 lastIndex)
- 继续调用 exec 方法,会先返回一个数组,包含第三。。。,以此类推,直到没有匹配的字符串,直接返回 null。(lastIndex 为 0)
如果继续调用 exec 方法,则会重新从第一步骤开始,如何往复。
let text = "cat, bat, sss, fff";
let pattern = /.at/g;
console.log(pattern.exec(text));
// [0: "cat", index: 0, input: "cat, bat, sss, fff", groups: undefined]
console.log(pattern.lastIndex); // 3
console.log(pattern.exec(text));
// [0: "bat", index: 5, input: "cat, bat, sss, fff", groups: undefined]
console.log(pattern.lastIndex); // 8
console.log(pattern.exec(text)); // null
console.log(pattern.lastIndex); // 0
console.log(pattern.exec(text));
// [0: "cat", index: 0, input: "cat, bat, sss, fff", groups: undefined]
console.log(pattern.lastIndex); // 3
u 标志
// 例子一,开启 u,字符视为字符序列
// '😄' unicode 码点为 U+1F604,UTF-16 编码为 0xD83D 0xDE04
const regex = /./;
const str = '😄';
console.log(str.match(regex));
// [0: '\uD83D', index: 0, input: '😄', groups: undefined]
// -----------------cut--------------
const regex = /./u;
const str = '😄';
console.log(str.match(regex));
// [0: '😄', index: 0, input: '😄', groups: undefined]
// 例子二,开启 u,允许使用 \u{…} 转义序列
const regex = /\u{1F604}/;
const str = 'Hello 😄';
console.log(regex.test(str)); // false
// -----------------cut--------------
const regex = /\u{1F604}/u;
const str = 'Hello 😄';
console.log(regex.test(str)); // true
s 标志
let pattern = /.+/;
const str = "ksid 54654\naa";
pattern.exec(str)
// [0:"ksid 54654", index: 0, input: "ksid 54654\naa", groups: undefined]
// ---------------- cut ------------------------
let pattern = /.+/s;
const str = "ksid 54654\naa";
pattern.exec(str)
// [0:"ksid 54654\naa", index: 0, input: "ksid 54654\naa", groups: undefined]
RegExp 实例属性
| RegExp 实例属性 | |
|---|---|
| 属性 | 说明 |
| global | 布尔值,表示是否设置了 g 标志 |
| ignoreCase | 布尔值,表示是否设置了 i 标志 |
| unicode | 布尔值,表示是否设置了 u 标志 |
| sticky | 布尔值,表示是否设置了 y 标志 |
| multiline | 布尔值,表示是否设置了 m 标志 |
| dotAll | 布尔值,表示是否设置了 m 标志 |
| lastIndex | 整数,表示在源字符串中下一次开始搜索的位置,默认为 0 开始 |
| source | 字符串,正则表达式的字面量字符串形式,没有开头和结尾的斜杠(例:/\[bc\]at/ 对应字符串 "\\[bc\\]at")。 |
| flags | 字符串,正则表达式中使用到的标志。 |
示例
let pattern1 = /\[bc\]at/gi;
console.log(pattern1.global); // true
console.log(pattern1.ignoreCase); // true
console.log(pattern1.multiline); // false
console.log(pattern1.lastIndex); // 0
console.log(pattern1.source); // \[bc\]at
// log 方法输出去的字符串,转义字符会被去除
pattern1.source // "\\[bc\\]at"
console.log(pattern1.flags); // gi
console.log(pattern1.dotAll); // false
console.log(pattern1.sticky); // false
console.log(pattern1.unicode); // false
RegExp 静态属性
注意!以下属性没有任何 Web 标准出处,因此不要在生产环境中使用它们。
| RegExp 静态属性 | ||
|---|---|---|
| 全名 | 简写 | 说明 |
| input | $_ | 最后搜索的字符串 |
| lastMatch | $& | 最后匹配的文本 |
| lastParent | $+ | 最后匹配的捕获组 |
| leftContext | $` | input 字符串中出现在 lastMatch 前面的文本 |
| rightContext | $' | input 字符串中出现在 lastMatch 后面的文本 |
示例
let text = "this has been a short summer";
let pattern = /(..)or(.)/g;
if(pattern.test(text)){
console.log(RegExp.input); // "this has been a short summer"
console.log(RegExp.lastMatch); // "short"
console.log(RegExp.lastParen); // "t"
console.log(RegExp.leftContext); // "this has been a "
console.log(RegExp.rightContext); // " summer"
}
简写形式,需要使用中括号语法,因为有的简写形式不是合法的 ECMASript 标识符。
另外,还可以通过 RegExp.$1~RegExp.$9 来获取捕获组
if(pattern.test(text)){
console.log(RegExp["$_"]); // "this has been a short summer"
console.log(RegExp["$&"]); // "short"
console.log(RegExp["$+"]); // "t"
console.log(RegExp["$`"]); // "this has been a "
console.log(RegExp["$'"]); // " summer"
console.log(RegExp["$1"]); // "sh"
console.log(RegExp["$2"]); // "t"
}
其它
当使用正则 test 和 exec 方法,起始位置是从正则对象的 lastIndex 属性开始的(字符串方法 String.prototype.matchAll 也是,而 match,replace,split,search 等,从 0 开始)。
如果有 g 或 y 标识,会更新 lastIndex,否则 lastIndex 总是默认为 0。
分组的名称
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = pattern.exec('2018-10-25');
// [0:"2018-10-25", 1:"2018", 2:"10", 3:"25", groups: {year: '2018', month: '10', day: '25'}, index: 0, input: "2018-10-25"]
console.log(result.groups.year); // 打印"2018"
console.log(result.groups.month); // 打印"10"
console.log(result.groups.day); // 打印"25"
后行断言
向前检查是否符合断言名称正则描述,零宽正向后行断言(?<= ) 前面要有xx,零宽负向后行断言(?<! )前面不能有 xx。
const pattern1 = /(?<=\$)\d+/u; // 正向后顾,匹配字符串中前面是$的数字
const result1 = pattern1.exec('$42');
console.log(result1[0]); // 打印42
const pattern2 = /(?<!\$)\d+/u; // 负向后顾,匹配字符串中前面不是是$的数字
const result2 = pattern2.exec('€42');
console.log(result2[0]); // 打印42
优先级
优先级从高到低
- 转义
\ - 括号
( )、(?: )、(?= )、[ ] - 字符和位置
- 或
|