什么是正则表达式
正则表达式是对文本模式的描述,类比Windows下查找文件使用的通配符理解。也就是*
和?
,*
可匹配多个字符,?
可匹配单个字符:*.doc
匹配所有word文档,aaa?.doc
匹配名字为aaa+一个字符的word文档。正则表达式表示更加复杂的文字模式,比如:电话号码等。
常见使用场景
- 文档中的查找与替换(notepad++,sublime text);
- Form表单验证:对form表单中的输入内容进行格式验证,例如:邮箱验证、url验证|密码格式验证等。
- 爬虫选取特定内容:通过匹配html中的片段,选择出感兴趣的部分
调试正则表达式的工具
JS中的正则表达式
JS支持两种正则表达式声明写法:
var pattern = new RegExp('ab');
var pattern2 = /ab/;
JS中根正则表达式相关的常用函数有三个
rexExp.test(string)
这个函数最为简单,匹配成功返回 true
,失败返回 false
var str = 'mafengshe';
console.log(/ma/.test(str)); // true
var str = 'mafengshe';
console.log(/\d/.test(str); // false
rexExp.exec(string)
exec 查找并返回当前的匹配结果,并以数组的形式返回。如果不存在模式,则返回 null,否则返回的总是一个长度为 1 的数组,其值就是当前匹配项。exec 方法受参数 g 的影响。若指定了 g,则下次调用 exec 时,会从上个匹配的 lastIndex 开始查找。
var str = "a1a2a3";
var reg = /a./;
console.log(reg.exec(str)[0]); // a1
console.log(reg.exec(str)[0]); // a1
var str = "a1a2a3";
var reg = /a./g;
console.log(reg.exec(str)[0]); // a1
console.log(reg.exec(str)[0]); // a2
console.log(reg.exec(str)[0]); // a3
console.log(reg.exec(str)[0]); // null
string.match(regExp)
注意:match是String对象的方法,不同于之前的test和exec,他们是RegExp对象的方法
这个方法最常用,他的结果和exec比较像,但是和exec不同的是如果指定参数g,match一次性返回素有结果,不需要多次调用。
var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var regexp = /[A-E]/gi;
console.log(str.match(regexp)); // ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e'];
正则表达式语法
常用元字符
代码 | 说明 |
---|---|
. |
匹配除换行符以外的任意字符 |
\w |
匹配字母或数字或下划线或汉字 |
\s |
匹配任意的空白符、包括空格,制表符(Tab)、换行符、中文全角空格等 |
\d |
匹配数字 |
\b |
匹配单词的开始或结束(零宽断言) |
^ |
匹配字符串的开始(零宽断言) |
$ |
匹配字符串的结束(零宽断言) |
例子:
console.log("a".match(/\w/)) // ["a", index: 0, input: "a"]
console.log("1".match(/\d/)) // ["1", index: 0, input: "1"]
console.log(" ".match(/\s/)) // [" ", index: 0, input: " "]
转义字符
正则表达式中(
,)
,[
,]
,{
,}
,.
,?
,+
,*
,^
,$
都有特殊的含义,如果你想查这些字符本身,而不是他的特殊意义,使用\
对字符进行转义。
重复
代码/语法 | 说明 |
---|---|
* |
重复零次或更多次 |
+ |
重复一次或更多次 |
? |
重复零次或一次 |
{n} |
重复n次 |
{n,} |
重复n次或更多次 |
{n,m} |
重复n到m次 |
例子:
console.log("aabbccc".match(/a+/)) // ["aa", index: 0, input: "aabbccc"]
console.log("aabbccc".match(/ab*c/)) // ["abbc", index: 1, input: "aabbccc"]
console.log("aabbccc".match(/ab?c/)) // null
console.log("aabbccc".match(/c{1,2}/)) // ["cc", index: 4, input: "aabbccc"]
字符集合
- 枚举
- 例如:
[aeiou]
匹配任何aeiou中的任何一个字符。
- 例如:
- 表示范围
[a-z]
匹配26个英文字母中的任意一个,类似用法还有[A-Z]
、[1-9]
。
- 元字符表示法
- 例如:
\d{8}
匹配八个数字。
- 例如:
例如:
[aeiou]
就匹配任何一个英文元音字母
[.?!]
匹配标点符号(.
或?
或!
)
反义
如果想表达除了某些字符以外,就需要用到反义。反义的表达方式主要有两种:
- 普通字符:
[^aeiou]
匹配不是aeiou的字符。 - 元字符: 改变大小写,如:
\W
(大小写)匹配不是字母、数字、下划线、汉字的字符
代码/语法 | 说明 |
---|---|
\W |
匹配任意不是字母,数字,下划线,汉字的字符 |
\S |
匹配任意不是空白符的字符 |
\D |
匹配任意非数字的字符 |
\B |
匹配不是单词开头或结束的位置 |
[^x] |
匹配除了x以外的任意字符 |
[^aeiou] |
匹配除了aeiou这几个字母以外的任意字符 |
分枝条件
分枝条件:如果几种规则中任何一种规则都应该当成可以匹配,则可以使用|
符号将几种规则隔开。
例如: 0\d{2}-\d{8}|0\d{3}-\d{7}
匹配两种电话号码,分别是三位区号-八位电话号码和四位区号-七位电话号码。
注意: 正则表达式的解析是从左到右的,如果满足了某个分支,则不会再去管其他条件。
分组
在重复中说明了如何匹配重复的单个字符,但如果需要对多个字符重复,则需要使用分组。例如IP地址。
用小括号将多个字符括起来,则会产生分组。可以对分组后的子表达式实施其他操作:例如重复。
当然我们也可以使用小括号,但是手动指定分组名称,或者不占用分组号,详细用法如下
代码/语法 | 说明 |
---|---|
(exp) |
匹配exp,并捕获文本到自动命名的组里 |
(?<name>exp) |
匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) |
(?:exp) |
匹配exp,不捕获匹配的文本,也不给此分组分配组号 |
IP地址匹配(粗糙):(\d{1,3}\.){3}\d{1,3}
注:正则表达式中没有数学表达式,因此若想去除匹配的无效IP地址:例如999.999.999.999,则只能使用分组、分支等方法,比较繁琐。
如果想完美的匹配ip地址,就要用冗长的 ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
,这种方法不直观,不利于维护,我们可以看看使用js来实现的代码
var testIp = ip =>
ip.split(".").length == 4 &&
ip.split(".").filter(i=>parseInt(i) >= 0 && parseInt(i) <= 255).length == 4
这个例子告诉我们,有的使用纯粹使用正则表达并不直观,也不利于实现,适当时候配合js实现判断效果是比较好的选择。
贪婪与懒惰
正则表达式的默认行为是匹配尽可能多的字符,即贪婪模式
console.log("aabab".match(/a.*b/)) // ["aabab", index: 0, input: "aabab"]
我们可以通过给重复量词(*
,+
,+
,?
,{n,m}
)后添加?
来开启非贪婪(懒惰)匹配
console.log("aabab".match(/a.*?b/)) // ["aab", index: 0, input: "aabab"]
后向引用
如果在匹配过程中,前面所匹配到的字符串后面需要用到,则需要用后向引用。例如想要匹配hello hello hello
这种重复的单词。
在正则表达式解析的过程中会对分组匹配的内容进行编号,之后可以便可以对匹配到的内容进行引用。
例如:
var str = "hello hello hello"
console.log(str.match(/\b(\w+)\b\s\1\s\1/)) //["hello hello hello", "hello", index: 0, input: "hello hello hello"]
零宽断言
接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b
,^
,$
那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。
代码/语法 | 说明 |
---|---|
(?=exp) |
匹配exp前面的位置(零宽断言) |
(?<=exp) |
匹配exp后面的位置(零宽断言) |
(?!exp) |
匹配后面跟的不是exp的位置(零宽断言) |
(?<!exp) |
匹配前面不是exp的位置(零宽断言) |
一个简单的记忆方法是,带!
都是取非,也就是不包括exp;不带<
都是预测断言,带<
的都是回顾断言。
注释
我们可以使用(?#comment)
在正则表达式包含注释
比如 2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)