什么是正则表达式

正则表达式是对文本模式的描述,类比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)

results matching ""

    No results matching ""