正则表达式真的要常用啊,要不然一下子就忘记了

前言

正则表达式是可以让我们处理字符串变得更加灵活简便,并且功能非常强大,因此在哪个编程语言中都有正则表达式的规则,极大的提高了程序员的工作效率,在此记录自己的学习过程,因为正则是我认为最容易忘记的匹配概念,巩固一遍也方便自己之后在此温习。

正则到底匹配了什么

永远记住:正则要么匹配字符,要么匹配位置,匹配字符能够让我们直接获取到需要的字符,匹配位置能够让我们清楚目标字符串的位置,再经过一系列的截取、替换或者插入,得到目标字符串。

字符匹配

两种维度的匹配

横向匹配

指一个正则可匹配的字符串的长度不是固定的,可以是多种情况,实现的方式是使用量词,譬如{m,n},表示出现最少m次,最多n次,如下例子:

1
2
3
var regex = /ab{2,5}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log(string.match(regex)) // [ 'abbc', 'abbbc', 'abbbbc', 'abbbbbc' ]

规则图例:

image

很容易理解,也就是m to n times,出现2到5次,这就是简单的横向匹配模式。

纵向匹配

纵向匹配是指,一个正则匹配的字符串,具体到某一位字符时,它可以是多种可能的值,在此指明,它是具体匹配某一位字符串,我们来看个简单例子:

1
2
3
var regex = /a[xyz]c/g;
var string = "aac abc axc ayz acc acccccz";
console.log(string.match(regex)) // [ 'axc' ]

这就是简单的纵向匹配,[xyz]指的是当匹配到第二位的时候,只能是x、y、z才能正确被匹配。

规则图例:

image

字符组

它指用一系列的规则来匹配特定某一位的字符,记住,一定是匹配某一位,比如这个字符要符合1到6的数字或者a到f,按照平常我们可以这样写[123456abcdef],固然可以,如果说条件太多了呢?比如说是所有非数字,非字母,这就比较难一一列出了。自然地,正则早就为我们提供了很多简写

范围表示法

这种表示法适用于比较有规律的规则,如下三种

  1. [1-8]表示1到8的某个数字
  2. [a-l]表示从a开始拼,到l的某个字母
  3. [A-K]与上面一点相同呢

例子:

1
2
3
var regex = /a[a-f1-7]c/g;
var string = "a3c alc a4c a0c akc";
console.log(string.match(regex)) // [ 'a3c', 'a4c' ]

这种是包含[]中的字符,如果说要排除这些在外呢?

排除字符串

纵向匹配时,到匹配某一位置,可以是任何东西,但是就不能为”1”,”g”,”x”,这时候就要用排除字符组,如下例子:

1
2
3
var regex = /a[^1gx]c/g;
var string = "a1c alc a4c a0c axc";
console.log(string.match(regex)) // [ 'alc', 'a4c', 'a0c' ]

简写形式

我们可以用[0-9]来表示来表示所有的数字,但是如果匹配规则太多,就容易混淆,所以正则为我们提供了很多的简写:

字符组 具体含义
\d 表示[0-9],数字
\D 表示[^0-9],大写就表示“非”,即非数字
\w 表示[0-9a-zA-Z_],表示数字大小写字母和下划线
\W 表示[^0-9a-zA-Z_],除数字大小写字母和下划线的所有字符
\s 表示[ \t\v\n\r\f], 空白符号
\S 表示非空白符
. 表示几乎所有字符,这个几乎并不是指所有

以上是常用的几种简写方式,如果说我们要匹配任意字符呢?可以用[\d\D]、[\w\W]、[\s\S]和[^]任意一种。

量词

简写形式

我们在之前的横向匹配就接触到了量词,它就像这样{m,n}匹配个数,但是如果说要匹配无限个、最少一个的话,用{m,n}来写就不是很美观,所以正则同样给量词提供了简写:

量词 含义
{m,} 表示至少出现m次
{m} 表示出现m次
? 表示出现或者不出现,可有可无的意思
+ 表示至少出现一次
* 表示出现任意次,也有可能不出现

举个例子:

1
var regex = /a{1,2}b{3,}d?e+f*/g;

规则图例:

image

贪婪匹配与惰性匹配

我们用一个简单的例子来对比两者的不同:

1
2
3
4
// 贪婪匹配
var regex = /\d{2,5}/g;
var string = "12312 1 23 3224 23123 124"
console.log(string.match(regex)) //[ '12312', '23', '3224', '23123', '124' ]

贪婪匹配:什么叫贪婪,就是匹配到了最低标准条件(两个数字)还不满足,还要尽量多的去匹配直到最大饱和(6个数字)才停止匹配。

1
2
3
4
// 惰性匹配
var regex = /\d{2,5}?/g;
var string = "12312 1 23 3224 23123 124"
console.log(string.match(regex)) //[ '12', '31', '23', '32', '24', '23', '12', '12' ]

惰性匹配:惰性就是只要满足最低要求,就停下这轮匹配开始下一轮,这个例子最低要求是2个数字,因此匹配到的都是两位数,注意它和贪婪匹配唯一的区别就是在量词后面加了”?”。

我们来对比他们的规则图例:

  • 惰性匹配:

image

  • 贪婪匹配:

image

他们俩的区别就是那根表示量词的线,惰性匹配是虚线,表示只要达到区间最低标准即可,而贪婪匹配则是实线,表示在区间内尽量多的匹配。

多选分支

上面所讲的横纵向匹配一般用来匹配一些有规律的字符,假如我们有这么一个要求,要匹配字符串中的某几个单次,比如boy或girl,这时候我们就可以用多选分支来匹配啦:

1
2
3
var regex = /boy|girl/g;
var string = "the girl say you are a good boy"
console.log(string.match(regex)) //[ 'girl', 'boy' ]

规则图例:

image

分析:我们可以看到,图例上的分支说明这两条路都可以走,这么说,上下两条路都是平等的吗?不一定,我们用个更有说服力的例子来解释。

1
2
3
var regex = /boy|boyfriend/g;
var string = "boyfriend"
console.log(string.match(regex)) // [ 'boy' ]
1
2
3
var regex = /boyfriend|boy/g;
var string = "boyfriend"
console.log(string.match(regex)) // [ 'boyfriend' ]
1
2
3
var regex = /boyfriend|boy/g;
var string = "boy"
console.log(string.match(regex)) // [ 'boy' ]

分析:例一例二为什么换了个位置,匹配出来的结果就是不一样的呢?

我们可以这么理解,分支匹配是种“优先惰性”匹配,优先是值优先考虑分支中最靠前的规则,惰性是指如果最前的规则一旦匹配上,就不会在继续。这样上面结果不同就很好解释了,第一个例子优先匹配第一个规则“boy”,并且是惰性匹配,匹配到boy就结束了。第二三个例子优先匹配boybriend如果有,则匹配boybriend(例二),如果没有就boy(例三)。

案例分析

匹配如 #FFBBAB #FEE

1
2
3
var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;
var string = "#ffbbad #FEB #fef #FDED3W"
console.log(string.match(regex)) // [ '#ffbbad', '#FEB', '#fef', '#FDE' ]

匹配url参数

1
2
3
var regex = /([^?&=]+)=([^?&=]*)/g;
var string = "https://juejin.im/post?name=huajinbo&age=12"
console.log(string.match(regex)) // [ 'name=huajinbo', 'age=12' ]

匹配日期 XXXX-XX-XX

1
2
3
var regex = /(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[0-1])/g;
var string = "2017-11-11"
console.log(regex.test(string)) //true

匹配HTML中的id属性

1
2
3
var regex = /id=".*?"/g;
var string = '<div id="main" class="top" ></div>'
console.log(string.match(regex)) //[ 'id="main"' ]

位置匹配

之前我们说过,正则表达式除了匹配字符还能匹配位置,怎么理解这个位置呢,就是两个字符之间符合正则规则的锚点,即如下图:

如何匹配位置

在ES5中有如下几个规则锚点:

规则 含义
^ 单行字符串起点处
$ 单行字符串末尾处
\b 单词[0-9a-zA-Z_]和非[0-9a-zA-Z_]的边界,即\w与\W的边界
\B 非(单词[0-9a-zA-Z_]和非[0-9a-zA-Z_])的边界
(?=p) 字符’p’前面的位置
(?!p) 非字符’p’前的位置

刚刚开始看这几个规则时,真是一脸懵逼,这和之前的\d \D \w \W到底是啥关系,其实并没有什么关系,我们要接受这么一个全新的概念,寻找字符串位置,也就是说以上六个规则就是为了寻找符合规则锚点的,话多不如来串代码:

^

1
2
3
var regex = /^/g;
var string = "hello"
console.log(string.replace(regex,'#')) // #hello#

对于一个字符串起点位置进行匹配,那么为什么要强调是单行呢?因为如果不是你特别指明多行匹配,正则就默认只匹配第一行的,如下:

1
2
3
4
5
6
7
var regex = /^/g;
var string = "hello\nhello"
console.log(string.replace(regex,'#'))
/*
#hello
hello
*/

这个时候就需要加上多行匹配规则,在/最后加上m(Multiline:多行),如下:

1
2
3
4
5
6
7
var regex = /^/gm;
var string = "hello\nhello"
console.log(string.replace(regex,'#'))
/*
#hello
#hello
*/

$

表示匹配单行字符串末尾位置,原理和^差不多,代码如下:

1
2
3
var regex = /$/gm;
var string = "hello"
console.log(string.replace(regex,'#')) // hello#

\b

单词[0-9a-zA-Z_]和非[0-9a-zA-Z_]的边界,即\w与\W的边界,这个就有点晦涩难懂,比如[空字符;’’];和32ADcx之间的位置,例子如下:

1
2
3
var regex = /\b/g;
var string = "[h'ello"
console.log(string.replace(regex,'#')) // [#h#'#ello#

这里我们注意空字符串也是非[0-9a-zA-Z_],这就是为什么我们在最后o后面还有一个#。

\B

这是我理解很久一个规则,怎么理解非(单词[0-9a-zA-Z_]和非[0-9a-zA-Z_])的边界,我们简单粗暴理解为:只要不是像[空字;’’];和32ADcx这样的区间,就是\B匹配的区间,比如db或[空字符,来一段代码表示:

1
2
3
var regex = /\B/g;
var string = "[h'ello"
console.log(string.replace(regex,'#')) // #[h'e#l#l#o

(?=p)

表示特点字符串前面的位置,这个很容易理解,代码:

1
2
3
var regex = /(?=o)/g;
var string = "boy"
console.log(string.replace(regex,'#')) // b#oy

(?!p)

表示非特点字符串前面的位置

1
2
3
var regex = /(?!o)/g;
var string = "boy"
console.log(string.replace(regex,'#')) // #bo#y#

案例分析

将12734893转换为12,734,893

1
2
3
var regex = /(?!^)(?=(\d{3})+$)/g;
var string = "12734893"
console.log(string.replace(regex,',')) // 12,734,893

常用正则

判断电话号码

1
var regx = /^1[34578]\d{9}$/;

验证邮箱

1
var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;

验证身份证号码

1
var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;

匹配汉字

1
var regx = /^[\u4e00-\u9fa5]{0,}$/;

去除首尾的’/‘

1
2
var str = '/asdf//';
str = str.replace(/^\/*|\/*$/g, '');

判断日期格式是否符合 ‘2017-05-11’的形式,简单判断,只判断格式

1
var regx = /^\d{4}\-\d{1,2}\-\d{1,2}$/

判断日期格式是否符合 ‘2017-05-11’的形式,严格判断(比较复杂)

1
var regx = /^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/;

IPv4地址正则

1
var regx = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

十六进制颜色正则

1
var regx = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/;

车牌号正则

1
var regx = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/;

过滤HTML标签

1
2
3
var str="<p>dasdsa</p>nice <br> test</br>"
var regx = /<[^<>]+>/g;
str = str.replace(regx, '');

密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符

1
var regx = /^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/;

URL正则

1
var regx = /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;

匹配浮点数

1
var regx = /^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$/;