「正则表达式」全解
前言
谈起正则表达式每次都是去看看文档,看了就忘,大部分实际用的时候都是谷歌一下答案,那么为了提升对正则表达式的理解,就让我们一起通过案例再次学习吧!!!
注明:以下知识内容是对 现代 JavaScript 教程-正则篇的笔记 ,一切请以官方为准,这里只是做了简单的笔记。
强烈推荐!!!
除此之外,额外加了一些小知识。
正则测试网站: 链接
正则轨道图网站:链接
修饰符
g
全局
i
不区分大小写
m
多行模式
s
启用 “dotall” 模式,允许点 .
匹配换行符 \n
u
开启完整的 Unicode 支持,该修饰符能够正确处理代理对
y
粘滞模式,在文本中的确切位置搜索
let str = "we we W WE we dWEd dWE dwed wed ddwe"
console.log( str.match(/we/gi) ) // 表示全局搜索 we 不区分大小写
// /we/i/ 表示匹配第一个 we, 没有匹配项,不会收到一个空数组,而是会收到 null
// 如果希望保底为数组
console.log( str.match(/we/i ) || [] )
// 替换
console.log( "we we W WE we dWEd dWE dwed wed ddwe".replace(/we/i, "hone") )
// 找到至少一个匹配项则返回 true,否则返回 false
/we/i.test(str) // true
字符类
正则 | 含义 |
---|---|
/\d/ | 数字:0-9 |
/\s/ | 空格符号:包括空格,制表符 \t ,换行符 \n 和其他少数稀有字符,例如 \v 、\f 和 \r |
/\w/ | 「单字」字符:拉丁字母或数字或下划线 _ 。非拉丁字母(如西里尔字母或印地文)不属于 \w |
let str = "+7(903)-123-45-67"
console.log( str.match(/\d/) ) // 7 匹配第一个
console.log( str.match(/\d/g) ).join('') // 查找所有,最终结果 79031234567
str.replace(/\D/g, "") // 查找非数字并删除,最终结果 79031234567
反向类
正则 | 含义 |
---|---|
/\D/ | 除 /d 以外的任何字符 |
/\S/ | 除 /s 以外的任何字符 |
/\W/ | 除 /w 以外的任何字符 |
点(.)匹配「任何字符」
点 .
它与除换行符之外的任何字符匹配, 默认情况下,点与换行符 \n
不匹配
写成 /h./s, 就可以匹配任何字符
对于空格,平时容易忽略
// 以下两种都可以匹配到
console.log("1 - 2".match(/\d - \d/))
console.log("1 - 2".match(/\d/s-\s\d/))
⚠️注意:在正则表达式中,所有字符都很重要。
Unicode:修饰符 "u" 和类 \p{...}
Unicode 属性 \p{…}
// \p{L} 表示任何语言中的一个字母
let str = "A ბ ㄱ"; alert( str.match(/\p{L}/gu) ) // A,ბ,ㄱ
console.log( str.match(/\p{L}/g) ); // null(没有匹配项,因为没有修饰符 "u")
修饰符 u
表示启用正则表达式中对 Unicode 的支持。
- 4 个字节长的字符被以正确的方式处理:被看成单个字符,而不是 2 个 2 字节长的字符。
- Unicode 属性可以被用于查找:
\p{…}
。
锚点:字符串开始 ^ 和末尾 $
插入符号 ^
匹配文本开头,而美元符号 $
则匹配文本末尾
// /^hone/ 的意思是字符串开始,紧接着就是 ^hone
/^hone/.test("hone age 18")
// /18$/ 是否以18结尾
/18$/.test("hone age 18")
测试完全匹配
// 所对应的匹配项必须正好在文本 '^' 的开头之后开始,并且结尾 '$' 必须紧跟其后
"12:24".test(/^\d\d:\d\d$/) // 可以匹配到
锚点 ^
和 $
属于测试,它们的宽度为零。
什么字符串可以匹配模式 ^$
? 答:空字符串是唯一的匹配项:它开始并立即结束
锚点 ^ $ 的多行模式,修饰符 "m"
m
启用多行模式修饰符 ,可以匹配每一行的^
和$
默认情况下,锚点
^
仅匹配文本的开头,在多行模式下,它匹配行的开头
搜索行的末尾 $
\d$
寻找每行的最后一个数字。
查找每行以数字开头,中间匹配重复出现1次或者更多次数字,并且以数字结尾的匹配字符
寻找新的一行
可以使用 \d\n 来寻找新的一行
词边界:\b
遇到 \b
时,它会检查字符串中的位置是否是词边界
⚠️注意: 词边界
\b
不适用于非拉丁字母
转义,特殊字符
转义
我们想要找到一个 +
号,,要将特殊字符用作常规字符,那么就需要在前面加上:\+
。
- 要在字面意义上搜索特殊字符
[ \ ^ $ . | ? * + ( )
,我们需要在它们前面加上一个反斜杠\
(“转义它们”)。
集合和范围 [...]
集合
[…]
表示查找满足的 "任意一个" 。
范围
方括号也可以包含 字符范围
字符类是某些字符集合的简写:
- \d —— 和
[0-9]
相同 - \w —— 和
[a-zA-Z0-9_]
相同 - \s —— 和
[\t\n\v\f\r ]
外加少量罕见的 Unicode 空格字符相同
排除范围
[^…]
"排除"范围匹配
[^aeyo]
—— 匹配除了'a'
、'e'
、'y'
或'o'
之外的任何字符。[^0-9]
—— 匹配除了数字之外的任何字符,与\D
作用相同。[^\s]
—— 匹配任何非空格字符,与\S
作用相同[^]
—— 表示任意字符,即对空值取反,得到全集
[…] 中的转义
在方括号中,我们可以使用绝大多数特殊字符而无需转义:
- 符号
. + ( )
无需转义。 - 在开头或结尾(未定义范围)的连字符
-
不会被转义。 - 插入符号
^
仅在开头会被转义(表示排除)。 - 右方括号
]
总是会被转义(如果我们需要寻找那个符号)
如果你为了“以防万一”转义了它们,这也不会有任何问题
范围和修饰符 “u”
如果集合中有代理对(surrogate pairs),则需要标志 u
才能使它们正常工作。
console.log( '𝒳'.match(/[𝒳𝒴]/u) ) // 𝒳
// 忘记写 u
'𝒳'.match(/[𝒳-𝒴]/); // Error: Invalid regular expression
量词 +, *, ? 和 {n}
{n}
在一个字符(或一个字符类,或 [...]
等)后附加一个量词,用来指出我们具体需要的数量。
\d{5}
表示 5 位数,与 \d\d\d\d\d
相同。
确切的位数:{5}
范围:{2,5}
,匹配 2-5 个
\d{2,}
查找大于2的数字
缩写
+
代表 一个或多个,与 {1,}
相同。
?
代表 零个或一个,与 {0,1}
相同。换句话说,它使得符号变得可选。
*
代表 零个及以上,与 {0,}
相同。也就是说,字符可以出现任何次数或者不出现。
贪婪量词和惰性量词
捕获组 (...)
- 方法
str.match
仅当不带修饰符g
时返回捕获组。 - 方法
str.matchAll
始终返回捕获组
// 域名搜索
"site.com my.site.com".match(/(\w+\.)+\w+/g) // site.com my.site.com
// 电子邮件
"my@mail.com @ his@site.com.uk".match(/[-.\w]+@([\w-]+\.)+[\w-]+/g) // my@mail.com, his@site.com.uk
嵌套组
let str = '<span class="my">'
let regexp = /<(([a-z]+)\s*([^>]*))>/
let result = str.match(regexp)
alert(result[0]) // <span class="my">
alert(result[1]) // span class="my"
alert(result[2]) // span
alert(result[3]) // class="my"
可选组
'a'.match(/a(z)?(c)?/)
命名组
// 这里的 <year> <month> <day> 就是对每个数组进行命名
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
let str = "2019-04-30";
// 匹配的组在 .groups 属性中
let groups = str.match(dateRegexp).groups;
alert(groups.year); // 2019
alert(groups.month); // 04
alert(groups.day); // 30
替换中的捕获组
$n
中的 n
是组号
let str = "John Bull";
let regexp = /(\w+) (\w+)/; // 这里分成两组
// $1 表示 John,$2 表示 Bull
alert( str.replace(regexp, '$2, $1') ); // Bull, John
// 命名的括号 引用为 $<name>
let regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
let str = "2019-10-30, 2020-01-01";
alert( str.replace(regexp, '$<day>.$<month>.$<year>') ); // 30.10.2019, 01.01.2020
非捕获组
通过在组的开头添加 ?:
来排除编号。
let str = "Gogogo John!";
// ?: 从捕获组中排除 'go'
let regexp = /(?:go)+ (\w+)/i;
let result = str.match(regexp);
alert( result[0] ); // Gogogo John(完整的匹配项)
alert( result[1] ); // John
alert( result.length ); // 2(在数组中没有其他数组项)
模式中的反向引用:\N
和 \k<name>
按编号反向引用:\N
let str = `He said: "She's the one!".`;
let regexp = /(['"])(.*?)\1/g;
alert( str.match(regexp) ); // "She's the one!"
代码执行过程:正则表达式引擎会找到第一个引号 (['"])
并记住其内容。那是第一个捕获组。
在模式中 \1
表示“找到与第一组相同的文本”,在我们的示例中为完全相同的引号。
与此类似,\2
表示第二组的内容,\3
—— 第三分组,依此类推。
- 注意:如果我们在捕获组中使用
?:
,那么我们将无法引用它。用(?:...)
捕获的组被排除,引擎不会记住它。 - 注意:在替换字符串中我们使用美元符号:
$1
,而在模式中 —— 使用反斜杠\1
。
按命名反向引用:\k<name>
要引用命名的捕获组,我们可以使用:\k<name>
let str = `He said: "She's the one!".`;
let regexp = /(?<quote>['"])(.*?)\k<quote>/g;
alert( str.match(regexp) ); // "She's the one!"
选择 (OR)
用竖线 |
表示
gr(a|e)y
等同于gr[ae]y
gra|ey
表示gra
或ey
前瞻断言与后瞻断言
前瞻断言
x(?=y)
仅在后面是 Y
时匹配 X
"1 turkey costs 30€".match(/\d+(?=€)/) // 30
否定的前瞻断言
X(?!Y)
,意思是“搜索 X
,但前提是后面没有 Y
"1 turkey costs 30€".match(/\d+(?!€)/) // 1
后瞻断言
- 肯定的后瞻断言:
(?<=Y)X
,匹配X
,仅在前面是Y
的情况下。 - 否定的后瞻断言:
(?<!Y)X
,匹配X
,仅在前面不是Y
的情况下。
"1 turkey costs $30".match(/(?<=\$)\d+/) // 30
"1 turkey costs $30".match(/(?<!\$)\b\d+/g)) // 1
捕获组
"1 turkey costs 30€".match(/\d+(?=(€))/) // 30, €
"1 turkey costs $30".match(/(?<=(\$))\d+/) // 30, $
灾难性回溯
粘性修饰符 "y",在位置处搜索
y
修饰符让我们能够在源字符串中的指定位置进行搜索
let str = 'let varName = "value"';
let regexp = /\w+/y;
regexp.lastIndex = 3;
alert( regexp.exec(str) ); // null(位置 3 有一个空格,不是单词)
regexp.lastIndex = 4; alert( regexp.exec(str) ); // varName(在位置 4 的单词)
正则表达式和字符串的方法
str.match(regexp)
在字符串 str
中查找 regexp
的匹配项。
- 如果
regexp
不带有修饰符g
,则它以数组的形式返回第一个匹配项,其中包含捕获组和属性index
(匹配项的位置)、input
(输入字符串,等于str
) - 如果
regexp
带有修饰符g
,则它将返回一个包含所有匹配项的数组,但不包含捕获组和其它详细信息。 - 如果没有匹配项,则无论是否带有修饰符
g
,都将返回null
str.matchAll(regexp)
它主要用来搜索所有组的所有匹配项。
与 match
相比有 3 个区别:
- 它返回一个包含匹配项的可迭代对象,而不是数组。我们可以用
Array.from
将其转换为一个常规数组。 - 每个匹配项均以一个包含捕获组的数组形式返回(返回格式与不带修饰符
g
的str.match
相同)。 - 如果没有结果,则返回的是一个空的可迭代对象而不是
null
。
str.split(regexp|substr, limit)
alert('12, 34, 56'.split(/,\s*/)) // 数组 ['12', '34', '56']
str.search(regexp)
方法 str.search(regexp)
返回第一个匹配项的位置,如果没找到,则返回 -1
str.replace(str|regexp, str|func)
用于搜索和替换的通用方法
// 用冒号替换连字符
alert('12-34-56'.replace("-", ":")) // 12:34-56
str.replaceAll(str|regexp, str|func)
这个方法与 str.replace
本质上是一样的,但有两个主要的区别:
- 如果第一个参数是一个字符串,它会替换 所有出现的 和第一个参数相同的字符串,而
replace
只会替换 第一个。 - 如果第一个参数是一个没有修饰符
g
的正则表达式,则会报错。带有修饰符g
,它的工作方式与replace
相同。
replaceAll
的主要用途是替换所有出现的字符串。
regexp.exec(str)
regexp.exec(str)
方法返回字符串 str
中的 regexp
匹配项。与以前的方法不同,它是在正则表达式而不是在字符串上调用的。
它的行为取决于正则表达式是否具有修饰符 g
。
如果没有修饰符 g
,则 regexp.exec(str)
会返回与 第一个匹配项,就像 str.match(regexp)
那样。这种行为并没有带来任何新的东西。
但是,如果有修饰符 g
,那么:
- 调用
regexp.exec(str)
会返回第一个匹配项,并将紧随其后的位置保存在属性regexp.lastIndex
中。 - 下一次这样的调用会从位置
regexp.lastIndex
开始搜索,返回下一个匹配项,并将其后的位置保存在regexp.lastIndex
中。 - ……以此类推。
- 如果没有匹配项,则
regexp.exec
返回null
,并将regexp.lastIndex
重置为0
。
regexp.test(str)
方法 regexp.test(str)
查找匹配项,然后返回 true/false
表示是否存在。
转载自:https://juejin.cn/post/7150650092658098189