本章JS正则表达式知识点来自JavaScript练习,把我认为比较基础的知识点整理了下,希望对大家有帮助!
目录
一、模式(Patterns)和修饰符(flags)
1.模式
有两种创建正则表达式对象的语法。
regexp
=
new
RegExp
(
"pattern"
,
"flags"
)
;
regexp
=
/
pattern
/
gmi
;
// 带有修饰符 g、m 和 i(后面会讲到)
new
RegExp()
动态创建
let
tag=
prompt
(
"What tag do you want to find?"
,
"h2"
)
;
let
regexp=
new
RegExp
(
`
<
${
tag}
>
`
)
;
// 如果在上方输入到 prompt 中的答案是 "h2",则与 /<h2>/ 相同
‘ / / ’为静态创建
斜线 /.../
的模式不允许插入表达式(如带有 ${...}
的字符串模板)。它是完全静态的。
2.修饰符
在 JavaScript 中,只有 6 个修饰符:
i
使用此修饰符后,搜索时不区分大小写:A
和 a
之间没有区别(请参见下面的示例)。
g
使用此修饰符后,搜索时会寻找所有的匹配项 —— 没有它,则仅返回第一个匹配项。
m
多行模式(详见 锚点 ^ $ 的多行模式,修饰符 "m")。
s
启用 “dotall” 模式,允许点 .
匹配换行符 \n
(在 字符类 中有详细介绍)。
u
开启完整的 Unicode 支持。该修饰符能够正确处理代理对。详见 Unicode:修饰符 "u" 和类 \p{...}
y
粘滞(Sticky)模式,在文本中的确切位置搜索
3.匹配:str.match()
str.match(regexp)
方法在字符串 str
中寻找 regexp
的匹配项。如果有匹配项返回数组,否则返回nul
//有匹配项
let str = "We will, we will rock you";
alert( str.match(/we/gi) ); // We,we(g-全局匹配,由两个匹配的子字符串构成的数组)
//没匹配项
let matches = "JavaScript".match(/HTML/); // = null
if (!matches.length) { // Error: Cannot read property 'length' of null
alert("Error in the line above");
}
4.替换:str.replace
str.replace(regexp, replacement)
方法使用 replacement
替换在字符串 str
中找到的 regexp
的匹配项(如果带有修饰符 g
则替换所有匹配项,否则只替换第一个)。
// 没有修饰符 g
alert
(
"We will, we will"
.
replace
(
/
we
/
i
,
"I"
)
)
;
// I will, we will
// 带有修饰符 g
alert
(
"We will, we will"
.
replace
(
/
we
/
ig
,
"I"
)
)
;
// I will, I will
5.regexp.test()
regexp.test(str)
方法寻找至少一个匹配项,如果找到了,则返回 true
,否则返回 false
。
let str = 'hello vue'
let regexp = /Vue/i
alert ( regexp.test(str) ) // true
二、字符类
注意:不要混淆字符类(内部)和修饰符(外部)
1.最常用的字符类
/d : 数字:从 0
到 9
的字符。
/s : 空格符号:包括空格,制表符 \t
,换行符 \n
和其他少数稀有字符,例如 \v
、\f
和 \r
。
/w : “单字”字符:拉丁字母或数字或下划线 _
。
例如:\d\s\w
表示“数字”,后跟“空格字符”,后跟“单字字符”,例如 1 a
。
let str = "+7(903)-123-45-67";
let regexp = /\d/g;
alert( str.match(regexp) ); // 匹配项构成的数组:7,9,0,3,1,2,3,4,5,6,7
// 让我们将其输出为纯数字构成的电话号码:
alert( str.match(regexp).join('') ); // 79031234567
alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // ' HTML5'
2.反向类
对于每个字符类,都有一个“反向类”,用相同的字母表示,但是大写的。
let str = "+7(903)-123-45-67";
alert( str.replace(/\D/g, "") ); // 79031234567
3.点(.)匹配任意字符
点 .
是一种特殊字符类,它与“除换行符之外的任何字符”匹配。
let regexp = /cs.4/
let str = 'cs-4'
alert( str.match(regexp) ) // cs-4
alert( "CS4".match(/CS.4/) ); // null,没有匹配项,因为这里没有与点匹配的字符
带有s修饰的点字符类 就能匹配任意字符
alert
(
"A\nB"
.
match
(
/
A.B
/
s
)
)
;
// A\nB(匹配了!)
4.Unicode:修饰符 "u" 和类 \p{...}
我感觉很少用到,就不展开介绍了,查看详情
三、锚点:字符串开始 ^ 和末尾 $
插入符号 ^
和美元符号 $
在正则表达式中具有特殊的含义。它们被称为“锚点”。
插入符号 ^
匹配文本开头,而美元符号 $
则匹配文本末尾。
let
goodInput=
"12:34"
;
let
badInput=
"12:345"
;
let
regexp=
/
^\d\d:\d\d$
/
;
alert
(
regexp.
test
(
goodInput)
)
;
// true
alert
(
regexp.
test
(
badInput)
)
;
// false
四、锚点 ^ $ 的多行模式,修饰符 "m"
多行模式由修饰符 m
启用。
它只影响 ^
和 $
的行为。
在多行模式下,它们不仅仅匹配文本的开始与末尾,还匹配每一行的开始与末尾。
let str = `1st place: Winnie
2nd place: Piglet
3rd place: Eeyore`;
console.log( str.match(/^\d/gm) ); // 1, 2, 3
五、 词边界:\b
词边界 \b
是一种检查,就像 ^
和 $
一样。
当正则表达式引擎(实现正则表达式搜索的程序模块)遇到 \b
时,它会检查字符串中的位置是否是词边界。
有三种不同的位置可作为词边界:
- 在字符串开头,如果第一个字符是单词字符
\w
。 - 在字符串中的两个字符之间,其中一个是单词字符
\w
,另一个不是。 - 在字符串末尾,如果最后一个字符是单词字符
\w
。
例如,可以在 Hello, Java!
中找到 \bJava\b
的匹配项,其中 Java
是一个独立的单词,而在 Hello, JavaScript!
中则不行。
alert
(
"Hello, Java!"
.
match
(
/
\bJava\b
/
)
)
;
// Java
alert
(
"Hello, JavaScript!"
.
match
(
/
\bJava\b
/
)
)
;
// null
在字符串 Hello, Java!
中,以下位置对应于 \b
:
六、转义,特殊字符
特殊字符在正则表达式中有特殊的含义,例如 [ ] { } ( ) \ ^ $ . | ? * +
。它们用于执行更强大的搜索
1.转义
假如我们想要找到一个点号 .
。
要将特殊字符用作常规字符,请在其前面加上反斜杠:\.
。
alert
(
"function g()"
.
match
(
/
g\(\)
/
)
)
;
// "g()"
2.new Regexp() 转义
如果我们使用 new RegExp
创建正则表达式,那么我们不必转义 /
,但需要进行一些其他转义。
提出问题:
alert
(
"\d\.\d"
)
;
// d.d 为什么不是原封不动的输出
在字符串中的反斜杠表示转义或者类似 \n
这种只能在字符串中使用的特殊字符。这个引用会“消耗”并且解释这些字符,比如说:
\n
—— 变成一个换行字符,\u1234
—— 变成该编码所对应的 Unicode 字符,- ……而当没有特殊含义时:如
\d
或者\z
,碰到这种情况时则会自动移除反斜杠。
所以调用 new RegExp
会获得一个没有反斜杠的字符串。这就是搜索不起作用的原因!
如果要修复这个问题,我们需要双斜杠,因为引用会把 \\
变为 \
:
let regStr = "\\d\\.\\d";
alert(regStr); // \d\.\d(现在对了)
let regexp = new RegExp(regStr);
alert( "Chapter 5.1".match(regexp) ); // 5.1
总之:静态转义(/ /)用 单斜杠 动态转义(new RegExp)用 双斜杠
七、集合和范围 [...]
1.集合
在方括号 […]
中的几个字符或者字符类表示“搜索给定字符中的任意一个”。
在正则表达式中,可以将集合和常规字符一起使用。
// 查找 [t 或 m],然后匹配 "op"
alert
(
"Mop top"
.
match
(
/
[tm]op
/
gi
)
)
;
// "Mop", "top"
请注意,虽然集合中有多个字符,但它们在匹配中只会对应其中的一个。
2.范围
方括号也可以包含 字符范围。
例如,[a-z]
表示从 a
到 z
范围内的字符,[0-5]
表示从 0
到 5
的数字。
alert
(
"Exception 0xAF"
.
match
(
/
x[0-9A-F][0-9A-F]
/
g
)
)
;
// xAF
[0-9A-F]
中有两个范围:它搜索一个字符,该字符要么是在 0
到 9
范围内的数字,要么是从 A
到 F
的字母。
我们也可以在 […]
中使用字符类。
例如,如果我们想查找单词字符 \w
或连字符 -
,则集合可以写为 [\w-]
。
- \d —— 和
[0-9]
相同, - \w —— 和
[a-zA-Z0-9_]
相同, - \s —— 和
[\t\n\v\f\r ]
外加少量罕见的 Unicode 空格字符相同。
3.排除范围
除了普通的范围匹配,还有像这样 [^…]
的“排除”范围匹配。
通过在开头添加插入符号 ^
来表示匹配所有 除了给定的字符 之外的任意字符。
alert
(
"alice15@gmail.com"
.
match
(
/
[^\d\sA-Z]
/
gi
)
)
;
// @ .
4.[...]中的转义
通常当我们想要准确地找到一个特殊字符时,我们需要像 \.
这样对其进行转义。如果我们需要反斜杠,那么我们需要使用 \\
,等等。
在方括号,我们可以使用绝大多数特殊字符而无需转义:
- 符号
. + ( )
无需转义。 - 在开头或结尾(未定义范围)的连字符
-
不会被转义。 - 插入符号
^
仅在开头会被转义(表示排除)。 - 右方括号
]
总是会被转义(如果我们需要寻找那个符号)。
……但是如果你为了“以防万一”转义了它们,这也不会有任何问题:
// 转义其中的所有字符
let
reg=
/
[\-\(\)\.\^\+]
/
g
;
alert
(
"1 + 2 - 3"
.
match
(
reg)
)
;
// 仍能正常工作:+,-
八、量词 +, *, ? 和 {n}
1.数量 {n}
确切位数
\d{5}
表示 5 位数,与 \d\d\d\d\d
相同。
alert
(
"I'm 12345 years old"
.
match
(
/
\d{5}
/
)
)
;
// "12345"
范围:
要查找 3-5 位的数字,我们可以将限制写在花括号中:\d{3,5}
alert
(
"I'm not 12, but 1234 years old"
.
match
(
/
\d{3,5}
/
)
)
;
// "1234"
我们可以省略上限。
那么正则表达式 \d{3,}
就会查找位数大于等于 3
的数字:
let str = "+7(903)-123-45-67";
let numbers = str.match(/\d{1,}/g);
alert(numbers); // 7,903,123,45,67
2.缩写
大多数常用的量词都有简写形式:
+
代表“一个或多个”,与 {1,}
相同。 /d+
?
代表“零个或一个”,与 {0,1}
相同。 例如,模式 ou?r
查找 o
,后跟零个或一个 u
,然后是 r
。
*
代表“零个及以上”,与 {0,}
相同。
九、贪婪量词和惰性量词
例子:获取每个双引号里的内容
let regexp = /".+"/g;
let str = 'a "witch" and her "broom" is one';
alert( str.match(regexp) ); // "witch" and her "broom"
……可以看出来它的运行结果与预期不同!
它没有找到匹配项 "witch"
和 "broom"
,而是找到:"witch" and her "broom"
。
这可被称为“贪婪是万恶之源”。
1.贪婪搜索
在贪婪模式下(默认情况),量词都会尽可能多地重复。
正则表达式引擎尝试用 .+
去匹配尽可能多的字符,然后在模式的其余部分不匹配时再将其逐一缩短。
2.惰性搜索
惰性模式中的量词与贪婪模式中的是相反的。它表示:“重复最少的次数”。
我们可以通过在量词后面添加一个问号 '?'
来启用它,这样匹配模式就变成了 *?
或 +?
,甚至将 '?'
变成 ??
。
这么说吧:通常问号 ?
本身就是一个量词(0 或 1),但如果将其放到 另一个量词(甚至是它自己)后面,就会有不同的含义 —— 它将匹配的模式从贪婪转为惰性。
正则表达式 /".+?"/g
能够按预期工作了:它找到了 "witch"
和 "broom"
:
let regexp = /".+?"/g;
let str = 'a "witch" and her "broom" is one';
alert( str.match(regexp) ); // "witch", "broom"
有一个跟惰性搜索相似的方法,替代方法;
3.替代方法
在我们的例子中,我们可以在不启用惰性模式的情况下使用正则表达式 "[^"]+"
找到带引号的字符串:
let regexp = /"[^"]+"/g;
let str = 'a "witch" and her "broom" is one';
alert( str.match(regexp) ); // "witch", "broom"
请注意,这个逻辑并不能取代惰性量词!
它们是不同的。我们在不同情况下可能会需要使用到其中的一个或另一个。
让我们再来看一个使用惰性量词失败而使用这种变体能获得预期结果的例子。
例如,我们想要找到 <a href="..." class="doc">
形式的带有任意 href
的链接。
let str = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
let regexp = /<a href=".*?" class="doc">/g;
// 错误的匹配!
alert( str.match(regexp) ); // <a href="link1" class="wrong">... <p style="" class="doc">
正确的变体可以是这样的:href="[^"]*"
。它会获取 href
特性中的所有字符直到最近的引号,正好符合我们的需求。
let str1 = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
let str2 = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
let regexp = /<a href="[^"]*" class="doc">/g;
// 有效!
alert( str1.match(regexp) ); // null,无匹配项,这是对的
alert( str2.match(regexp) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
十、捕获组
模式的一部分可以用括号括起来 (...)
。这被称为“捕获组(capturing group)”。
这有两个影响:
- 它允许将匹配的一部分作为结果数组中的单独项。
- 如果我们将量词放在括号后,则它将括号视为一个整体。
例子:括号将字符组合,所以 (go)+
匹配 go
,gogo
,gogogo
等。
let
regexp=
/
[-.\w]+@([\w-]+\.)+[\w-]+
/
g
;
alert
(
"my@mail.com @ his@site.com.uk"
.
match
(
regexp)
)
;
// my@mail.com, his@site.com.uk
数组里是为所有满足条件的元素
1.匹配中括号的内容
方法 str.match(regexp)
,如果 regexp
没有修饰符 g
,将查找第一个匹配项,并将它作为数组返回:
- 在索引
0
处:完整的匹配项。 - 在索引
1
处:第一个括号的内容。 - 在索引
2
处:第二个括号的内容。 - ……等等……
现在,我们在结果数组中得到了标签的整体 <h1>
及其内容 h1
:
let
str=
'<h1>Hello, world!</h1>'
;
let
tag=
str.
match
(
/
<(.*?)>
/
)
;
alert
(
tag[
0
]
)
;
// <h1>
alert
(
tag[
1
]
)
;
// h1
2.嵌套组
括号可以嵌套。在这种情况下,编号也从左到右。
例如,在搜索标签 <span class="my">
时,我们可能会对以下内容感兴趣:
- 整个标签的内容:
span class="my"
。 - 标签名称:
span
。 - 标签特性:
class="my"
。
让我们为它们添加括号:<(([a-z]+)\s*([^>]*))>
。
这是它们的编号方式(根据左括号从左到右):
验证:
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"
3.可选组
例如,让我们考虑正则表达式 a(z)?(c)?
。它查找 "a"
,后面是可选的 "z"
,然后是可选的 "c"
。
let match = 'ac'.match(/a(z)?(c)?/)
alert( match.length ); // 3
alert( match[0] ); // ac(完整的匹配项)
alert( match[1] ); // undefined, 因为没有 (z)? 的匹配项
alert( match[2] ); // c
4.带有组搜索所有匹配项:matchAll()
注意:旧的浏览器不支持 matchAll
。
就像 match
一样,它寻找匹配项,但有 3 个区别:
- 它返回的不是数组,而是一个可迭代的对象。
- 当存在修饰符
g
时,它将每个匹配项以包含组的数组的形式返回。 - 如果没有匹配项,则返回的不是
null
,而是一个空的可迭代对象。
例如:
let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
// results —— 不是数组,而是一个迭代对象
alert(results); // [object RegExp String Iterator]
alert(results[0]); // undefined (*)
results = Array.from(results); // 让我们将其转换为数组
alert(results[0]); // <h1>,h1(第一个标签)
alert(results[1]); // <h2>,h2(第二个标签)
5.命名组
用数字记录组很困难。对于简单的模式,它是可行的,但对于更复杂的模式,计算括号很不方便。我们有一个更好的选择:给括号命名。
在左括号后紧跟着放置 ?<name>
即可完成对括号的命名。
例如,让我们查找 “year-month-day” 格式的日期:
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
let str = "2019-04-30";
let groups = str.match(dateRegexp).groups;
alert(groups.year); // 2019
alert(groups.month); // 04
alert(groups.day); // 30
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
let str = "2019-10-30 2020-01-01";
let results = str.matchAll(dateRegexp);
for(let result of results) {
let {year, month, day} = result.groups;
alert(`${day}.${month}.${year}`);
// 第一个 alert:30.10.2019
// 第二个:01.01.2020
}
6.前瞻断言与后瞻断言
前瞻断言:x(?=y)
语法为:x(?=y)
,它表示“仅在后面是 Y
时匹配 X
”。这里的 X
和 Y
可以是任何模式。
那么对于一个后面跟着 €
的整数,正则表达式应该为:\d+(?=€)
。
let
str=
"1 turkey costs 30€"
;
alert
(
str.
match
(
/
\d+(?=€)
/
)
)
;
// 30,数字 1 被忽略了,因为它后面没有 €
更复杂的测试也是可能的,例如 X(?=Y)(?=Z);
表示:
我们同时在寻找 X
后跟 Y
和 Z
。
这只有在模式 Y
和 Z
不是互斥的情况下才可行。
否定前瞻断言:X(?!Y)
语法是:X(?!Y)
,意思是“搜索 X
,但前提是后面没有 Y
”。
let
str=
"2 turkeys cost 60€"
;
alert
(
str.
match
(
/
\d+\b(?!€)
/
g
)
)
;
// 2(价格不匹配)
同理:
肯定的后瞻断言:(?<=Y)X
匹配 X
,仅在前面是 Y
的情况下。
否定的后瞻断言:(?<!Y)X
,
匹配 X
,仅在前面不是 Y
的情况下。
本章提供对正则表达式基础的知识点,想了解更多js正则表达式,可前往网站