【前端】JavaScript基础(正则表达式)
Dandelion 9/2/2022 JS
# 概述
- 模式和修饰符
- 字符类
- 锚点:字符串开始(^)和末尾($)标识符
- 词边界:\b
- 特殊字符和转义
- 集合和范围
- 量词:+/*/?/{n}
- 贪婪量词和惰性量词
- 捕获组
- 模式中的反向引用
- 选择(OR)|
- 前瞻断言与后瞻断言
- 灾难性回溯
- 粘性修饰符 y
- 正则表达式和字符串的方法
- 参考 (opens new window)
# 模式和修饰符
- 创建 RegExp 对象的两种语法
regexp = new RegExp("pattern");
:可动态创建表达式regexp = /pattern/;
:不允许插入表达式
- 修饰符(6 个)
i
:搜索时不区分大小写g
:搜索时会寻找所有的匹配项,否则仅返回第一个匹配项m
:多行模式s
:启用 dotall 模式,允许点.
匹配换行符\n
u
:开启完整的 Unicode 支持,能够正确处理代理对y
:粘滞模式,在文本中的确切位置搜索
- 搜索:
str.match(regexp)
- 若正则表达式包含修饰符
g
,返回一个由所有匹配项所构成的数组 - 若没有修饰符,则会以数组形式返回第一个匹配项
- 如果没有匹配项,则返回 null
- 注:若希望结果始终是一个数组,则
matches = str.match(regexp) || []
- 若正则表达式包含修饰符
- 替换:
str.replace(regexp, replacement)
- 若带有修饰符
g
,则替换所有匹配项,否则只替换第一个
- 若带有修饰符
- 测试:
regexp.test(str)
- 至少找到一个匹配项,如果找到了,则返回 true,否则返回 false
# 字符类
- 数字类:
\d
,对应于任何一位数字,即从0
到9
的字符 - 空格符:
\s
,包括空格、制表符\t
,换行符\n
和其他少数稀有字符,例如\v
、\f
和\r
- 单字字符:
\w
,拉丁字母或数字或下划线 - 非数字:
\D
,除\d
以外的任何字符 - 非空格:
\S
,除\s
以外的任何字符 - 非单字:
\W
,除\w
以外的任何字符,例如非拉丁字母或空格 .
:与换行符\n
之外的任何字符匹配(带有修饰符\s
时,.
匹配任何字符)- 任何字符:
[\s\S]
或[\d\D]
或[^]
...
# 字符串开始和末尾标识符
^
:匹配文本开头$
:匹配文本末尾^...$
:用于测试一个字符串是否完全匹配一个模式- 在多行(
\m
)模式下,匹配每一行的开始与结尾 - 注:锚点
^
和$
属于测试,宽度为零,区别于换行符\n
# 词边界
- 有三种不同的位置可作为词边界(
\b
)- 在字符串开头,如果第一个字符是单词字符
\w
- 在字符串中的两个字符之间,其中一个是单词字符
\w
,另一个不是 - 在字符串末尾,如果最后一个字符是单词字符
\w
- 在字符串开头,如果第一个字符是单词字符
\b
既可以用于单词,也可以用于数字(如,\b\d\d\b
查找独立的两位数)- 词边界测试
\b
检查该位置的一侧是否匹配\w
,而另一侧则不匹配\w
# 特殊字符和转义
- 特殊字符:
[ ] { } ( ) \ ^ $ . | ? * +
,使用反斜杠进行转义 - 如果在
/.../
内(但不在new RegExp
内),需要转义斜杠 - 如果没使用
/.../
,而是使用另一种new RegExp
的方式创建正则表达式,则不需要转义斜杠 - 当将字符串传递给给
new RegExp
时,我们需要双反斜杠\\
,因为字符串引号会消耗一个反斜杠
# 集合和范围
- 在
[]
中的几个字符或者字符类表示:搜索给定字符中的任意一个 [abc]
:表示集合,字母 a/b/c 中的任意一个[0-9a-z]
:表示范围,从 0 到 9 或 a 到 z 范围内的任意一个字符([\s\d]
表示一个空格或一个数字)[^0-9]
:表示排除范围,匹配除了数字之外的任何字符,与\D
作用相同
# 量词
{n}
:表示数量。例如,\d{5}
表示 5 位数;\d{3,5}
表示 3-5 位的数字;\d{3,}
表示查找位数大于等于 3 的数字+
:代表一个或多个,与{1,}
相同。例如,\d+
表示所有数字?
:代表零个或一个,与{0,1}
相同,即字符是可选的*
:代表零个及以上,与{0,}
相同,即字符可以出现任何次数或者不出现
# 贪婪量词和惰性量词
- 贪婪模式:默认情况下,量词会尽可能多地向后匹配,然后当模式的剩余部分不匹配时,再回溯直至匹配剩余的部分
- 惰性模式:通过在量词后面添加一个
?
来启用,如*?
、+?
、??
等。量词尽可能少地向后匹配 - 惰性模式的另一种等同的做法是:使用排除项微调贪婪搜索,如
/"[^"]+"/g
# 捕获组
- 模式的一部分可以用括号括起来,称为捕获组
- 它允许将匹配的一部分作为结果数组中的单独项
- 如果将量词放在括号后,则它将括号视为一个整体
- 包括:嵌套组、可选组以及命名组
/(go)+/i
:可以匹配gogogo
或Gogo
等(括号被从左到右编号)str.match(regexp)
:如果regexp
没有修饰符g
,将查找第一个匹配项,并将它作为数组返回- 在索引 0 处:完整的匹配项
- 在索引 1 处:第一个括号的内容
- 在索引 2 处:第二个括号的内容
- 以此类推
- 嵌套组:嵌套的括号,编号同样从左到右
- 例如,
<(([a-z]+)\s*([^>]*))>
- 索引 0 中始终保存的是正则表达式的完整匹配项
- 例如,
- 可选组:即使组是可选的且在匹配项中不存在,也存在相应的
result
数组项,并且等于undefined
- 例如,
a(z)?(c)?
- 索引 0 中始终保存的是正则表达式的完整匹配项
- 例如,
- 当搜索所有匹配项(带修饰符
g
)时,match
方法不会返回组的内容- 例如,
str.match(/<(.*?)>/g)
结果是一个匹配数组,但没有每个匹配项的详细信息 - 使用
str.matchAll(regexp)
进行搜索- 它返回的不是数组,而是一个可迭代的对象(使用
Array.from
将其转换为数组) - 当存在修饰符 g 时,它将每个匹配项以包含组的数组的形式返回
- 如果没有匹配项,则返回的不是
null
,而是一个空的可迭代对象
- 它返回的不是数组,而是一个可迭代的对象(使用
- 例如,
- 命名组:在左括号后紧跟着放置
?<name>
即可完成对括号的命名- 例如,
(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})
- 匹配的组在
groups
属性中:str.match(dateRegexp).groups.year
- 例如,
str.replace(regexp, replacement)
中的 replacement 可以使用$n
来交换组的内容,其中 n 是组号- 例如,
"John Bull".replace(/(\w+) (\w+)/, '$2, $1')
,变为"Bull, John"
- 对于命名的括号,引用为
$<name>
- 例如,
- 非捕获组:通过在开头添加
?:
来排除组- 例如,
"Gogogo John!".match(/(?:go)+ (\w+)/i)
,结果数组中便删除了第一个组 - 索引 0 中始终保存的是正则表达式的完整匹配项
- 例如,
# 模式中的反向引用
- 按编号反向引用:\N,其中
N
是组号- 例如,
/(['"])(.*?)\1/g
,找到第一个引号(['"])
并记住其内容 - 可以确保模式查找的结束引号与开始的引号完全相同
- 若在捕获组中使用
?:
,将无法引用它。对于使用(?:...)
捕获的组被排除,引擎不会记住它
- 例如,
- 按命名反向引用:
\k<name>
- 例如,
/(?<quote>['"])(.*?)\k<quote>/g
- 例如,
# 选择
|
:允许在多个字符中进行选择- 例如,
gr[ae]y
等同于gr(a|e)y
- 例如,
gra|ey
表示gra
或ey
- 要将选择应用于模式中一部分内容的选择,可以将其括在括号中:
love (HTML|CSS)
匹配love HTML
或love CSS
- 例如,
# 前瞻断言与后瞻断言
- 前瞻断言:
X(?=Y)
,表示仅在后面是Y
时匹配X
- 当查找
X(?=Y)
时,正则表达式引擎会找到X
,然后检查其后是否有Y
。如果没有,则跳过潜在匹配,并继续搜索。 X(?=Y)(?=Z)
匹配X
后跟Y
和Z
(模式Y
和Z
不互斥)- 例如,
"1 turkey costs 30€".match(/\d+(?=€)/)
结果是30
- 注:前瞻断言只是一个测试,括号
(?=...)
中的内容不包含在匹配结果中 - 否定的前瞻断言:
X(?!Y)
,匹配X
,但前提是后面没有Y
- 当查找
- 后瞻断言:匹配前面有特定字符串的模式
- 注:非 V8 引擎的浏览器不支持后瞻断言
- 肯定的后瞻断言:
(?<=Y)X
,匹配X
,仅在前面是Y
的情况下- 例如,
"1 turkey costs $30".match(/(?<=\$)\d+/)
,结果是30
- 例如,
- 否定的后瞻断言:
(?<!Y)X
,匹配X
,仅在前面不是Y
的情况下- 例如,
"2 turkeys cost $60".match(/(?<!\$)\b\d+/g)
,结果是2
- 例如,
- 捕获前瞻断言和后瞻断言所匹配的内容,或者部分内容:只需将该部分包装在额外的括号中
- 例如,
"1 turkey costs 30€".match(/\d+(?=(€|kr))/)
,结果是30, €
- 例如,
"1 turkey costs $30".match(/(?<=(\$|£))\d+/)
,结果是30, $
- 例如,
# 灾难性回溯
- 有些正则表达式看起来很简单,但执行起来耗时却非常长,甚至会导致 JavaScript 引擎挂起
- 由于贪婪模式或惰性模式的存在,使得引擎遍历了所有排列组合,才得出答案
- 解决方法:
- 减少可能的组合数量:
/^(\w+\s?)*$/
---/^(\w+\s)*\w*$/
- 禁止量词的回溯:在常规量词之后添加
+
,则常规量词就变成了占有型量词- 例如,可以使用
\d++
替代\d+
来阻止回溯 - 占有型量词实际上比常规量词更简单,尽可能多地匹配,没有任何回溯
- 基于前瞻变换模拟:在不回溯的情况下尽可能多地重复
\w
的模式可以写为(?=(\w+))\1
- 例如,可以使用
- 减少可能的组合数量:
# 粘性修饰符 y
y
:在源字符串中的指定位置进行搜索regexp.lastIndex
与regexp.exec(str)
regexp = /\w+/y
# 正则表达式和字符串的方法
str.match(regexp)
str.matchAll(regexp)
str.split(regexp|substr, limit)
str.search(regexp)
str.replace(str|regexp, str|func)
str.replaceAll(str|regexp, str|func)
regexp.exec(str)
regexp.test(str)
- 参考 (opens new window)