JS正则表达式快速入门
我们都爱正则表达式,但是一个个奇怪字符的组合总是让我这种弱鸡感到难以领悟。
每次看到一个正则匹配式却理解不了,我都安慰自己:“反正我已经知道它是用来匹配文本的”
为什么不现在直接把它学会呢?
首先推一个可以帮助你理解正则的网站正则表达式可视化
还有一个 Atom 插件 regex-railroad-diagram
这样的可视化模型可以帮助你快速回忆与熟悉正则字符的含义。
1.字符
正则表达式中我们可以使用两种字符:
- 原义字符:原义文本字符代表字符本身的匹配
- 元字符:元字符是正则表达式中有特殊含义的字符,其组合代表特殊字符或者其他逻辑
在正则表达式中以下字符有特殊含义,为元字符
所以只有转义之后它们才能作为原义文本字符来使用:
*
, +
, ?
, $
, ^
, .
, |
, \
, ()
, []
, {}
比如你想要匹配字符串中出现的$
, 必须在正则表达式中使用\$
进行转义
特殊字符
这几个组合而成的元字符组合会匹配相应特殊字符:
字符 | 匹配特殊字符 |
---|---|
\t |
制表符 |
\v |
垂直制表符 |
\n |
换行符 |
\r |
回车 |
\f |
换页 |
c* |
Ctrl+特定字符 |
2.字符匹配方式
正常匹配:
在正则表达式中使用原义字符或组合成的特殊字符会正常匹配相应字符
在正则表达式中使用a
会匹配文本a1b2ab3
中第一个字符a
和倒数第三个字符a
字段匹配:
在正则表达式中使用连续的原义字符或组合而成的特殊字符会匹配相应字符组合
在正则表达式中使用ab
会匹配文本a1b2ab3
中倒数第二三个字符ab
类匹配:
在正则表达式中使用元字符:
[ ]
: 创建类将匹配[ ]
中的任意字符
类中字符的连写代表或的关系——满足连写的字符其中一者就将会被匹配
正则表达式中使用[ab]
会匹配文本a1b2ab3
中所有的a
与b
字符
特殊的类:
[^]
: 反相匹配——匹配不是[^
和]
中的任意字符[a-z]
: 小写字母匹配——从a
到z
的小写英文字符[A-Z]
: 大写字母匹配——从A
到Z
的大写英文字符[0-9]
: 数字匹配——从0
到9
的数字字符
同时有一些等价的元字符方便使用:
元字符 | 等价类 |
---|---|
. | [^\r\n] |
\d | [0-9] |
\D | [^0-9] |
\s | [\t\n\v\f\r] |
\S | [^\t\n\v\f\r] |
\w | [a-zA-Z_0-9] |
\W | [^a-zA-Z_0-9] |
具体什么含义?根据之前的线索推断一下吧 : )
量匹配
有的时候,正则表达式需要对字符出现次数进行匹配,此时我们需要使用量匹配的元字符
字符 | 含义 |
---|---|
? |
最多出现一次 |
+ |
至少出现一次 |
* |
出现任意次 |
{n} |
出现n次 |
{n,m} |
出现n到m次 |
{n,} |
至少出现n次 |
还包含其它的逻辑匹配
字符 | 结果 |
---|---|
x(?=y) |
只有当x 后面紧跟着y 时,才匹配x |
x(?!y) |
只有当x 后面不是紧跟着y 时,才匹配x |
x|y |
匹配x 或y |
量匹配的元字符跟随在字符的后面生效,比如:
T\d?
匹配后面最多出现一次数字字符的字符T
默认情况下,正则表达式处于贪婪模式,即尽可能多地匹配字符串,因此匹配结果应该是a1234
如果想进入非贪婪模式,需要在量词后面加一个问号:a\d{2,4}?
,此时正则表达式一旦匹配成功就不会继续匹配
此时匹配结果为a12
分组匹配
在正则表达式中使用()
可以在正则表达式中生成“组”
“组”在字符组合的时候具有相对的优先级:
当我们的正则表达式复杂的时候,我们需要分组进行元字符和字符的组合:
比如我们使用[a-z]\d{3}
匹配a1b2c3d4
就是无效的,因为量匹配仅对其前面的字符有效,这个正则表达式的意思就是匹配一个后面跟随三个数字的字母
当我们想要匹配另一种含义——“连续出现三次字母+数字时”,我们就需要采用分组匹配的方式:
使用正则表达式([a-z]\d){3}
匹配a1b2c3d4
就会得到a1b2c3
同时我们可以使用分组匹配来配合逻辑元字符进行不同逻辑的匹配效果,比如:
使用正则表达式Hel(lo|en)
对字符串Hello Helen!
进行匹配,匹配得到的结果为
Hello
和Helen
“组”除了优先级的提升,还有其他的特殊效果:
分组匹配可以在替换的时候配合$
进行反向引用,反向引用会在后面的章节进行讲解
同时也可以通过exec
方法被分拣到数组里面(见后面章节)
边界匹配
边界匹配是采用元字符的一种特殊匹配方式:
字符 | 含义 |
---|---|
^ |
匹配行首的边界 |
$ |
匹配行末的边界 |
\b |
匹配单词边界 |
\B |
匹配非单词边界 |
这些字符本身并不匹配字符,匹配的是特定的边界
在字符串There HiThere There
中使用(全局模式)
- 正则表达式
^
/$
结果替换为#
——#There HiThere There
与There HiThere There#
- 正则表达式
^T
/e$
结果替换为#
——#here HiThere There
与There HiThere Ther#
- 正则表达式
\b
结果替换为#
——#There# #HiThere# #There#
- 正则表达式
\bT
结果替换为#
——#here HiThere #here
- 正则表达式
\bThere\b
—— 匹配第一个单词There
与最后一个单词There
- 正则表达式
\BThere\b
—— 匹配HiThere
里面的There
根据这些规律的提示,加上你自己的练习,掌握边界匹配不需要太多时间。
修饰符
修饰符是针对于每个正则表达式设置的属性(它们之间互相不冲突):
修饰符 | 含义 |
---|---|
g |
全局模式:对字符串从头开始不止一次地进行匹配 |
i |
忽略大小写模式:忽略英文字母大小写 |
m |
多行模式:令元字符^ 和$ 在多行模式下进行工作(行是由\n 或\r 分割的) |
y |
黏性模式:只从lastIndex 位置开始匹配(且不试图从任何之后的索引匹配) |
1 | var reg = /\w/g |
而且,前文说过,我们之前的实例全部在默认使用全局模式,如果不在全局模式之下,正则表达式只会匹配字符串从头开始的第一个字符段
2.JavaScript 中的 RegExp 对象
在JavaScript
中,由/
开始,由/
结束(或者跟随有效正则修饰符)的字段会被JavaScript
编译器正确解析为一个RegExp
对象。
同时//
—— 空的正则表达式会被解析为注释
RegExp
是JavaScript
中的内置对象,其构造函数创建了一个正则表达式对象,用来匹配文本
RegExp
的详细特性在mdn上有详细讲解与实例
RegExp
有两种构造方式:
- 字面量方式
- 构造函数方式
字面量方式
RegExp
对象的实例由/
+正则表达式正文+/
+正则表达式修饰符组成
1 | var reg = /\bis\b/g |
构造函数
1 | var reg = new RegExp('\\bis\\b', 'g') |
RegExp
构造函数接受两个参数:
- 包含正则表达式的一个字符串(注意在字符串中转义符需要转义输入)
- 包含正则表达式修饰符的一个字符串
因此我们更倾向于使用字面量方式进行 RegExp
实例的构建
RegExp对象的属性
JavaScript
中,RegExp
有以下属性:
RegExp.lastIndex
RegExp.prototype.global
RegExp.prototype.ignoreCase
RegExp.prototype.mutiline
RegExp.prototype.source
RegExp.prototype.sticky
以上属性中,除了lastIndex
和source
以外,其它属性都描述着RegExp
对象的在构造时附件的修饰模式 – 由构造RegExp
时附加的修饰符决定(它们都是布尔值)
source
属性是RegExp
对象正则表达式正文的字符串 —— 正则表达式/
内部的内容
lastIndex
是个复杂的属性,它相当RegExp
上的一个标记,它可写,指定着下一次匹配的起始索引位置,同时也是匹配的上一次结果尾部后一个字符的索引位置(只有在全局模式下该属性才有效)
1 | var reg = /\w/ |
得到结果为1
,2
,3
全局正则表达式的匹配可以配合lastIndex
进行匹配异步的驱动
有很多与正则表达式有关的方法可以让我们更轻松地进行字符串操作:
RegExp对象的方法
JavaScript
中,RegExp
有以下方法:
RegExp.prototype.exec()
RegExp.prototype.test()
RegExp.prototype.toString()
exec
方法接受一个字符串作为参数,返回一个数组:
- 第一个元素是正则表达式匹配到的文本
- 之后的元素都是依次排列的,正则表达式中从前到后每个分组匹配到的文本
- 数组中存在两个索引:
index
: 匹配到字段开始的索引input
: 匹配的整体字符串
- 正则表达式匹配不到返回
null
全局模式RegExp
对象调用exec
时,可以多次执行exec
方法来查找同一个字符串中的其它的成功匹配,每次exec
都会更新lastIndex
属性
test
方法接受一个字符串作为参数,如果正则表达式可以匹配到字符串里的字段,返回true
,否则返回false
exec
方法和test
方法在全局的RegExp
对象执行时都会更新lastIndex
:
1 | var reg = /\w\d/g, |
这个表格是测试后的lastIndex
变化规律:
lastIndex 索引范围 |
下一次匹配结果 | 下一次匹配lastIndex 的调整 |
---|---|---|
大于字符串的长度 | 一定失败 | 0 |
等于字符串的长度,RegExp 不匹配空字符串 |
一定失败 | 0 |
等于字符串的长度,RegExp 匹配空字符串 |
成功匹配到空字符串 | 不变 |
小于字符串的长度 | 在索引位置向后进行匹配的结果 | 下一次匹配到字段的后一位,匹配不到则归0 |
toString
方法则返回是正则表达式的全文的字符串
其他与RegExp对象相关的方法
String.prototype.match()
String.prototype.replace()
String.prototype.search()
String.prototype.split()
match
与RegExp
的exec
方法相似,接受一个RegExp
对象为参数,返回一个包含匹配结果的数组:
- 在非全局模式下,返回的数组包含和
RegExp.exec
一样的内容 - 在全局模式下,返回包含所有匹配字段的数组
- 如果不能匹配到,则返回
null
replace
方法接受两个参数:第一个参数是RegExp
或字符串,第二个参数为字符串或函数,字符串调用replace
之后会将字符串中第一个参数正则匹配到的/出现的替换为某个字符串,详细请看mdn
第二个参数作为字符串的话可以使用$
号来进行反向引用:
1 | '2016-10-10'.replace(/(\d{4})-(\d{2})-(\d{2})/g, '$2$3$1') |
除了分组匹配,还有:
变量名 | 代表的值 |
---|---|
$$ |
插入个$ |
$& |
插入匹配的子串 |
`$`` | 插入当前匹配的子串左边的内容 |
$' |
插入当前匹配的子串右边的内容 |
第二个参数作为函数的话有这几个(至少三个以上)个参数:
- 第
1
个参数:匹配到的文本字符串 - 第
2
到第n
个参数:分组匹配到的内容(设进行了n-1
次分组匹配) - 第
n+1
个参数:匹配项在字符串中的index
- 第
n+2
个参数:原字符串
1 | 'a1a2a3a4'.replace('a','b') //b1a2a3a4 |
search
方法与RegExp
的test
相似,
接受一个RegExp
对象为参数,但是返回值为第一次匹配到字段的索引数值(如果没有匹配到则返回-1
)
split
方法接受两个参数,第一个参数为分隔符(可以是字符串和RegExp
),第二个参数为片段数量的限定值(可选),返回一个由一个个分隔符之间的字段组成的数组
如果不存在有效参数,则返回整个字符串在第一位的数组
如果分隔符是一个空字符串,则会把原字符串中每个字符的装在数组里返回
如果分隔符是包含分组匹配的正则表达式,则每次匹配到结果时,分组匹配到的结果也会插入到返回的数组中
1 | var reg1 = /\*/, |
3.总结
写了这么多,笔者相信你们头都晕了 : )
记住正则表达式的关键:
在Javascript
中
正则表达式是用来进行文本匹配相关操作的一个特殊的内建对象
- 匹配自身的的原义字符
- 匹配特殊字符或者调整匹配逻辑的元字符
这两种字符的组合成正则表达式的正文,配合修饰符,去匹配字符串中的文本字段