正则
RegExp
对象表示正则表达式,它是对字符串执行模式匹配的强大工具。
RegExp
: Regular(正规) Expression(表达式)
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑
它描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
正则表达式的特点
- 灵活性、逻辑性和功能性非常的强;
- 可以迅速地用极简单的方式达到字符串的复杂控制。
- 对于刚接触的人来说,比较晦涩难懂。
- 就算会写,写的过程没问题,写完再看几乎不认识。
比如:
匹配国内电话号码: /\d{3}-\d{8}|\d{4}-\d{7}/
验证手机号: /^((13[0-9])|(15,[^4,\D])|(18[0,5-9]))\d{8}$/
很难记住
创建方式
- 字面量形式创建
/pattern/attributes
- 构造函数的方式创建
new RegExp(pattern, attributes);
参数
pattern
是一个字符串,指定了正则表达式的模式或其他正则表达式。参数
attributes
是一个可选的字符串,包含属性 “g”、”i” 、”m”、”u” 和 “y”,分别用于指定全局匹配、区分大小写的匹配、多行匹配、开启完整的 unicode 支持和粘滞模式。
1 | // 1. 字面量形式创建 /pattern/attributes |
使用方式
str.search(reg)
返回找到的匹配项的索引位置,如果没找到则返回 -1。总是查找第一个匹配项。
1 | var str = "hello world"; |
regexp.test(str)
检验字符串是否有符合正则条件的字符串片段,返回 true/false
基本上和
str.search(reg) != -1
一样,
1 | var str = "I love JavaScript"; |
str.match(reg )
返回符合正则表达式的字符串片段
不使用
"g"
修饰符的时候,结果是一个数组,里面有该匹配项和额外的属性:index
– 匹配项在字符串中所处在的位置,input
– 原始字符串。
当使用
"g"
修饰符的时候,str.match
就会返回由所有匹配项组成的数组
1 | console.log("hello".match(/l/)); // ["l", index: 2, input: "hello", groups: undefined] |
regexp.exec(str)
检索字符串中指定的值。返回找到的值,并确定其位置
- 如果正则没有
g
,那么regexp.exec(str)
返回第一个匹配项,也就是str.match(reg)
。 - 如果正则有
g
,那么regexp.exec(str)
返回第一个匹配项,然后在regexp.lastIndex
里 记住 该匹配项结束的的位置。下一次调用从regexp.lastIndex
开始搜索,并且返回下一个匹配项。如果再没有匹配项了,则regexp.exec
返回null
,regexp.lastIndex
置为0
。
reg.lastIndex
(可读写) 标示开始下一次匹配的字符位置 跟reg.exec()
协调使用
1 | var str = "abababa"; |
y 修饰符
y
修饰符意味着搜索应该并且只能在属性regexp.lastIndex
指定的位置查找匹配项。通常搜索是在整个字符串里搜索匹配的子串的。但是当有了
y
修饰符,则只会在regexp.lastIndex
(默认是0
)指定的位置开始查找。
例如:
1 | var str = "I love JavaScript!"; |
y 修饰符我们一般只在提高解析器性能的时候使用
str.split(regexp|substr, limit)
使用 regexp(或子字符串)作为分隔符分隔字符串。
我们之前已经使用过 split
的字符串形式,像下面这样:
1 | alert('12-34-56'.split('-')) // [12, 34, 56] |
但是我们也可以传入一个正则表达式:
1 | alert('12-34-56'.split(/-/)) // [12, 34, 56] |
str.replace(str|reg, str|func)
最简单的用处 — 搜索和替换子字符串,就像下面这样:
1 | // 把横线替换成冒号 |
当 replace
的第一个参数是字符串时,它只会查找第一个匹配项。
如果要找到所有的横线,我们不能使用字符串 "-"
,而是 regexp /-/g
,带上 g
修饰符。
1 | // 用冒号替换所有的横线 |
第二个参数是要替换的字符串。
我们可以在里面使用特殊符号:
符号 | 插入 |
---|---|
$$ |
"$" |
$& |
整个匹配项 |
$ ` |
匹配项前面的字符串部分 |
$' |
匹配项后面的字符串部分 |
$n |
如果 n 是一个 1-2 位的数字,那么这表示从左到右数第 n 个括号的内容 |
在下面的例子中,使用 $&
将所有的 "John"
替换成 "Mr.John"
:
1 | var str = "John Doe, John Smith and John Bull."; |
圆括号通常与 $1
,$2
一起使用,就像下面的例子:
1 | var str = "John Smith"; |
对于那些需要“智能”替换的场景,第二个参数可以是函数。
它会在每次匹配时被调用,并且其返回值会替换掉匹配项。
例如:
1 | var i = 0; |
在上面的示例中,函数每次只返回下一个数字,但通常结果是基于匹配的。
调用该函数 func(str, p1, p2, ..., pn, offset, s)
的参数是:
str
— 表示匹配项,即()
正则表达式中的内容 如/a([1-9])/
如果有匹配项,它的值将会是a
加上1-9
之间的一个数字p1, p2, ..., pn
— 圆括号里的内容(如果有的话),有几个捕获组就有几个这样的参数offset
— 匹配项所在的位置,s
— 源字符串。
如果在 regexp 中没有圆括号,那么该函数总是有 3 个参数:func(str, offset, s)
。
返回值将会替换第一个参数的位置的内容。
下面的代码展示了匹配的所有信息:
1 | // 显示并且替换所有的匹配项 |
在下面的例子中,有两个圆括号,所以 replacer
有 5 个参数:str
是完全匹配项,然后是圆括号,然后是 offset
和 s
:
1 | function replacer(str, name, surname, offset, s) { |
使用函数赋予了我们终极替换大招,因为这能获取所有的匹配信息,并且能够访问外部变量,可以做任何事情。
组合 []
表达式 | 介绍 |
---|---|
[adgk] | 查找给定集合内的任何字符。 |
[0-9] | 查找任何从 0 至 9 的数字。 |
[a-z] | 查找任何从小写 a 到小写 z 的字符。 |
[A-Z] | 查找任何从大写 A 到大写 Z 的字符。 |
[A-z] | 查找任何从大写 A 到小写 z 的字符。 |
边界
- ^ 以什么开头 /^a/.test(“ba”)
- $ 以什么结尾 /a$/.test(“ba”)
- 注意:^在[]中表示 非 例如:/[^a]/.test(“b”)
量词
* 重复零次或更多 >=0
+ 重复一次或更多次 >=1
? 重复零次或一次 (0 || 1) /[a-z]?/.test(“aa”)
{n} n次 (x=n) /[1-9]{5}/.test(“1234”)
{n,} 重复n次或更多 (x>=n) “a123b4567”.match(/[0-9]{2,}/g)
{n,m} 重复出现的次数比n多但比m少 (n<=x<=m) /[0-9]{3,5}/.test(“a123bcd”)
x|y x或者y
/a{1,3}?/ 能取1个不取三个 ?加在量词之后表示打破贪婪匹配,能取少,不取多,就是不贪婪匹配
1
2
3
4
5
6
7
8"aaaabbbaaaaaaaaabbaaaa".match(/a{3,5}/g);
// log: ["aaaa", "aaaaa", "aaaa", "aaaa"]
"aaaaaaaaaaaaaaaaa".match(/a+/g);
// log: ["aaaaaaaaaaaaaaaaa"]
"aaaaaaaaaaaaaaaaa".match(/a+?/g);
// log: ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"]
"aaaabbbaaaaaaaaabbaaaa".match(/a{3,5}?/g);
// log: ["aaa", "aaa", "aaa", "aaa", "aaa"]
捕获组 ()
正则模式的一部分可以用括号括起来 (...)
,由此构成一个『捕获组』。每次成功捕获后,RegExp
对象中,会保存最近一次正则的捕获组,如 RegExp.$1
表示最近一次正则匹配的第一个捕获组。
注意:如果加上 g 修饰符, 正则的捕获组将不会被放到结果数组中, 只会出现完整的匹配结果
这有两个作用:
- 当使用
String.match
或RegExp.exec
方法时,它允许你把匹配到的部分放到一个独立的数组项里面。 - 如果我们在括号之后加上量词,那么它会应用到这个整体,而非最后一个字符。
例如:
1 | var str = "Hahaha welcome"; |
如果没有括号,模式 /ha+/
则表示 h
之后跟上一个或多个 a
。比如: haaaaa
,捕获括号将 ha
划为了一组。
捕获结果的首项是完整的匹配结果,之后就是各个捕获组,从左往右依次排开。如果某个捕获组是可选的,且在匹配中没有找到对应的项,那么在相应的匹配结果中,该项依然会存在(值为 undefined)。
例如:
1 | var str = "a"; |
非捕获组 ?:
某些时候,我们会希望使用括号来正确设置量词,但是并不希望其内容出现在结果数组中。可以通过在开头加上 ?:
从而在结果中排除该组。
例如,我们希望找到 (ha)+
,但是并不希望其内容(ha
)出现在单独的数组项中,那么我们可以这样写:(?:ha)+
。
1 | var str = "Hahaha interesting"; |
元字符
名称 | 含义 |
---|---|
. | 查找单个字符,除了换行和行结束符。 |
\w | 查找单词字符。英语字母表中的一个字母或者一个数字或一个下划线 |
\W | 查找非单词字符。 |
\d | 查找数字。 |
\D | 查找非数字字符。 |
\s | 查找空白字符。 |
\S | 查找非空白字符。 |
\b | 匹配单词边界。 |
\B | 匹配非单词边界。 |
注意:\b 和 \B 是根据方向存在的
环视断言
当我们想根据前面/后面的上下文筛选出一些东西的时候,前瞻断言和后瞻断言(通常被称为“环视断言”)对于简单的正则表达式就很有用。
环视断言类型:
模式 | 类型 | 匹配 |
---|---|---|
x(?=y) |
前瞻肯定断言 | 匹配 x ,仅当后面跟着 y |
x(?!y) |
前瞻否定断言 | 匹配 x ,仅当后面不跟 y |
(?<=y)x |
后瞻肯定断言 | 匹配 x ,仅当跟在 y 后面 |
(?<!y)x |
后瞻否定断言 | 匹配 x ,仅当不跟在 y 后面 |
例如:
1 | var str = "2 servings of rice are worth 30¥"; |