Web前端之路让前端飞

正则表达式必知必会-读书笔记

2017-08-24  本文已影响244人  小貔

什么是正则表达式

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

简单的说,正则表达式是一些用来匹配和处理文本的字符串.利用正则表达式我们可以查找特定的信息,或者替换掉文本中的特定信息.

并且正则表达式搜索的能力和替换文本的能力极为强大,而且其也比较容易学习和掌握.

那么正则表达式是什么样子的,有什么规则,又该如何使用呢?如下面这样的语句:

注:所有举例都是选用javascript编程语言中的正则表达式,所有可能会与其他编程的正则表达式有些出入

 /b[Cc][Aa]/

这就是一个正则表达式,它可以匹配bca,bCa,bcA,bCA这些字符串.所以我想现在你应该对于正则表达式有些概念了.

接下来我们来介绍一些正则表达式的规则和语法.

正则表达式

正则表达语法结构

正则表达式的语法结构如下:

/正则表达式主体/修饰符(可选)

我们来举一个实例来分析一下

/runoob/i

/runoob/i 是一个正则表达式。

runoob 是一个正则表达式主体 (用于检索)。

i 是一个修饰符 (搜索不区分大小写)。

所以该正则表达式可以匹配Runoob,rUnoob等不区分大小写的内容符合runoob的字符串集.

正则表达式修饰符

修饰符是影响整个正则规则的特殊符号,会对匹配结果和部分内置函数行为产生不同的效果,JavaScript正则表达式中,包含以下三个修饰符:

修饰符 描述
i 执行对大小写不敏感的匹配
g 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止
m 执行多行匹配

匹配单个任意字符

在正则表达式里,我们用.符号来匹配任何一个单个的字符. 例:


var str1 = 'ab'
var str2 = 'a.b'
var str3 = 'acb'
var str4 = 'ac.b'
var reg = /a.b/

console.log(reg.test(str1)) //-> false
console.log(reg.test(str2)) //-> ture 
console.log(reg.test(str3)) //-> true
console.log(reg.test(str4)) //-> false

匹配特殊字符

从上个例子中,我们了解到.字符在正则表达式里有着特殊的含义. 如果我们的模式里需要一个.那该怎么办?

这时,我们可以在.加上\(反斜杠)字符来对它进行转义,使得.表示的是它自身的文本内容,而不是它在正则表达式中的特殊含义.例:

  
var str1 = 'baidu.com' 
var str2 = 'baidu@com'

var reg1 = /baidu\.com/
var reg2 = /baidu.com/
 
console.log(reg1.test(str1))  // -> true
console.log(reg1.test(str2))  // -> false  分析:  字符`@`与字符'.'不匹配 

console.log(reg2.test(str1))  // -> true  分析:  `.`可以匹配任意一个字符, 包括'.'字符本身.
console.log(reg2.test(str2))  // -> true

.一样,/也是一个特殊字符, 如果你想要匹配/内容本身,应该用//来表示.除此,正则表达式中还有很多特殊字符,我们会在下面慢慢介绍.

匹配字符区间(一组字符)中的某一个

在正则表达式中,我们可以使用元字符[]来定义一个字符集合. 这两个元字符之间的所有字符都是该集合的组成部分.字符集合的匹配结果是能够与该集合里的任意一个成员相匹配的文本.例:

// 写一个正则表达式可以匹配'ca.xls'和'na.xls'.
var reg  = /[cn]a.xls/
// 列举几个字符串来验证正则
var s1 = 'ca.xls'
var s2 = 'na.xls'
var s3 = 'sa.xls'
 
console.log(reg.test(s1)) // -> true
console.log(reg.test(s2)) // -> true
console.log(reg.test(s3)) // -> false
/[0123456789]/ 

上述正则表达式可以用来匹配0到9这个10个数字中任意一个.

/[abcdefg]/

上述正则表达式可以用来匹配a-g字符中任意一个字符.

上述正则表达式虽然可以表达一些字符区间,但是其写法有些繁琐.为此,正则表达式中提供了一些简化的写法:

区间 说明
[0-9] 匹配0到9之间的数字.
[A-Z] 匹配A到Z之间的所有大写字母.
[a-z] 匹配a到z之间的所有小写字母.
[A-F] 匹配A到F之间的所有大写字母.
[a-f] 匹配a到f之间的所有小写字母.
[A-z] 匹配ASCII字符A到ASCII字符z的所有字母.(不过不常用,因为其中还包含[^等排列在Za之间的字符)

提示:在定义一个字符区间的时候,一定要避免让这个区间的尾字符小于它的首字符. 例如 [3-1]这种区间就是没有意义的,而且往往会让整个模式失效

字符集合通常用来指定一组必须匹配其中之一的字符. 但是有时候我们需要翻过来做,给出一组不需要得到的字符.这时,我们可以用元字符^来对一个字符集合进行取非匹配. 例:

var reg = /[^0-9]/ 
var str1 = '1'
var str2 = 'a'

console.log(reg.test(str1)) // -> false
console.log(reg.test(str2)) // -> true

这个正则表达式匹配的是任何不是数字的字符.

提示: 我们可以把取非理解成数学中的获取补集

类元字符

字符集合(匹配多个字符中的某一个)是最常见的匹配的形式,而一些常用的字符集合可以用特殊元字符来代替可以用特殊元字符来代替.这些元字符匹配的是某一类别的字符(术语称之为'类元字符').类元字符并不是必不可少的东西,但用它们构造出来的正则表达式简明易懂,在实践中很有用.

之前我们介绍过,[0-9]是[0123456789]的简写形式,它可以用来匹配任何一个数字.不过你若是想匹配的除数字以外的其他东西,那么对这个集合取写成[^0-9]便可以了.下面我们列出了用来匹配数字和非数字的类元字符.

元字符 说明
\d 任何一个数字字符(等价于[ 0-9 ])
\D 任何一个非数字字符(等价于[ ^ 0-9 ] )

例:

var reg = /\d/
var str1 = '7'
var str2 = 'a'

console.log(reg.test(str1)) // ->true
console.log(reg.test(str2)) // -> false
console.log(/\D/.test(str2)) // -> true

字母和数字---A到Z(不区分大小写),数字0到9,再加上下划线字符_是另一种比较常用的字符集合;这些字符 常见于各种名字里(如目录名,变量名等等). 其类元字符如下:

元字符 说明
\w 任何一个字母数字字符(大小写均可)或下划线字符(等价于[a-zA-Z0-9_])
\W 任何一个非字母数字字符(大小写均可)或下划线字符(等价于[^ a-zA-Z0-9_])

个人认为\w中的w可能word这个单词的简写,所以我跟喜欢称\w为单词字符.
例:

var reg = /\w+/
var str1 = 'abc123_'
var str2 = '-;~@'

console.log(reg.test(str1)) // ->true
console.log(reg.test(str2)) // -> false
console.log(/\W+/.test(str2)) // -> true

匹配空白字符

在介绍如何匹配空白字符,我们先来说一些小常识.

  1. 回车与换行

    • 回车的转义字符是/r , 回车是光标回到当前行的开始位置.
    • 换行的转义字符是/n, 换行是光标转到当前位置的下一行相同的位置.

因此,在DOS和WINDOWS系统上,回车和换行必须连用,从而实现文 本字符的换行并回到下一行的起始位置.包括HTTP也使用CRLF来表示 每一行的结束.

而在UNIX,LINUX中,只有换行码而不需要回车码,系统默认换行就是回车加换行。

但是,在MAC上,Mac OS 9 以及之前的系统的换行符是CR\r ,从 Mac OS X (后来改名为“OS X”)开始的换行符是 LF\n,和Unix/Linux统一了。

  1. 如何表示一个空白行

在Windows中用\r\n\r\n来代表一个空白行.而在Linux中用\n\n来代表一个空白行.

我们可以用正则表达式[\r]?\n[\r]?\n来匹配不同系统中的一个空白行.

关于空白行的匹配,我想你也许会有这样一个疑问: 遇到\r\n\n或者\n\r\n\这两种情况,该如何? 如果遇见这两种情况的话的确会被上述正则表达式所匹配.但是一般情况下,这两种情况极为少见(通常只有人为的拼接换行符时才会出现这种情况).所以你可以安心使用,如果你还觉得不放心的话,可以用/[(\r\n\r\n)(\n\n)]/来匹配空白行.

  1. 空格和空白字符

    其实空格只是字符中的一种,它并不是转义字符. ' '这便是空格字符的表示方法.
    下面是一个用正则来验证空格字符的:

     var str = 'hello world'
     var str2 = 'helloworld'
     var reg = / /
     console.log(reg.test(str))  // -> true
     console.log(reg.test(str2)) // -> false
    

    但是在正则表达式中可以用\s来匹配空白字符.需要注意的是空白字符并不等同于空格字符,或者说空格字符是空白字符的一个成员.空白字符有:

    字符 说明
    \n 换行字符
    \f 换页符
    \r 回车符号
    \t 制表符
    \v 垂直制表符
    " " 空格(js中的特有的)
 因此上述代码也可以改为如下:
 
```js
var str = 'hello world'
var str2 = 'helloworld'
var reg = /\s/
console.log('str',reg.test(str),'str2',reg.test(str2))
// str true str2 false
```

匹配十六进制和八进制

我们可以用前缀\x来匹配十六进制值. 例如\x0A对ASCII中的序号为10的符号(换行符),其效果等价于\n.

我们用前缀\0来匹配八进制值.例如\011对应于ASCII中的序号为9的符号(制表符),其效果等价于\t.

重复匹配

我们可以给字符(或者字符集合)加上一个+作为后缀,来匹配一个或多个(至少一个,不匹配0个字符的情况)字符.


var reg = /ba+/ 
// 匹配 "baaa","ba", 但不匹配"b".

var reg = /[0-9]+/
//匹配任意数字串的组合.

var reg = /[0-9+]/
/*
 这样写�虽然是合法正则式,但是不能匹配一个或者多个数字.
 它代表的是 [0123456789+]这个集合中的任意一个字符.
*/

我们可以给字符(或者字符集合)加上一个*作为后缀,来匹配零个或多个字符. 例:

var reg = /ba*/
var str1 = 'baaaa'
var str2 = 'b'

console.log(reg.test(str1)) // -> ture
console.log(reg.test(str2)) // -> ture

我们可以给字符(或者字符集合)加上一个?作为后缀,来匹配零个或多个字符.例:

// 匹配http或者https,可以用如下正则表达式
var reg = /http[s]?/ // => http或者https
// 匹配不同系统中的空白行
reg = /[\r]?\n[\r]?\n/ //=> \r\n\r\n 或者\n\n
  var  reg =  /#[0-9A-Fa-f]{6}/  // 可以用来匹配任意一个RGB值.
  reg.test('#486D3A') // true
  reg.test('#3F3F46') // true
  reg.test('#3F3F4')) //false
   var reg = /\d{2,4}/ 
   reg.test('1') // -> false 
   reg.test('12') // -> true
   reg.test('1234') // -> true

{3,}表示至少重复3次. 如:

 var reg=/\d{3,}/
 reg.test('1') // -> false 
 reg.test('1234') // -> true

防止过度匹配

我们利用正则表达式来获取文本中的a标签,如下例:

var str = ' this offer is not available to customers living in <a>AK</a> and <a>HI</a>'

var reg = /<a>.*<\/a>/g 

str.match(reg)  // 匹配结果: <a>AK</a> and <a>HI</a>

我们发现虽然没有漏掉我们想要匹配的文本,但是并不是我们预期的结果, 我们想要的结果是分别返回两个标签,而不是一个包含两个标签的文本.

这是因为,*+都是所谓的贪婪型元字符,它们进行匹配时的行为模式是多多益善而不是适可而止的.它会尽可能的从一段文本的开头一直匹配到这段文本的结尾,而不是从这段文本的开头到到第一个匹配时为止.

那么问题来了,如果我们不需要这种贪婪行为�,该怎么办呢?答案便是用这些元字符懒惰版本.即匹配尽可能少的字符.下面是几个常用的贪婪型元字符和它的懒惰版本:

贪婪型元字符 懒惰型元字符
* *?
+ +?
{n,} {n,}?

现在我们修改上一次的代码,使它达到我们的预期.

var str = ' this offer is not available to customers living in <a>AK</a> and <a>HI</a>'
var reg = /<a>.*?<\/a>/g 
 str.match(reg)  // 匹配结果: ["<a>AK</a>", "<a>HI</a>"]

我们应根据具体情况来选用'贪婪型'或'懒惰型'元字符.

位置匹配

在某些场合中,我们需要对某段文本的特定位置进行匹配,这是就需要使用位置匹配来解决了.

提示:位置匹配符,只匹配位置而不匹配内容.这点极为重要,需要牢记

我们用限定符\b(boundary)来指定单词的边界,\b可以用来匹配一个单词的开头或结尾.

\b匹配的是这样一个位置: 这个位置位于一个能够用来构成单词的字符(字母,数字以及下划线.即\w)和一个不能用来够成单词的字符(即\W)之间.

我们可以用|来模拟边界符,为字符串"the captain wore his cap and cape proundly"

结果如下:

|the| |captain| |wore| |his| |cap| |and| |cape| |proundly|

如果到这里你对与这个单词边界符\b还有些疑惑. 我们可以再举个例子:

 字符串: "baidu.com" 

我们知道上述字符串中的.并不在\w中.所以我们认为它是一个非单词字符.而.两边分别是字母u和字母c,它们都属于\w,都是单词字符.

如上述的关于单词边界的定义解释一样,单词边界是一个能够用来构成单词的字符(字母,数字以及下划线.即\w)和一个不能用来够成单词的字符(即\W)之间的位置.我们为字符串加上边界,如下:

|baidu|.|com

单词字符非单词字符之间的位置就是单词边界.

接下来我们来看看一个使用\b的例子:

var str1 = 'and cap  proundly '
var str2 = 'and cape  proundly '
 
var reg= /\bcap\b/

// 匹配字符串是否有"cap"这个单词
console.log(reg.test(str1)) // -> true 
console.log(reg.test(str2)) // -> false

字符串边界可以用来进行与字符串有关的�位置匹配(字符串的开头,结尾,等等.)

  1. ^匹配字符串的开头

我们用^来匹配一个字符串的开头位置.用法如下:

  var str1 = 'I will persist until I succeed.'
  var str2 = 'You will persist until I succeed.'
  
  var reg = /^I.*?/

  reg.test(str1) // true
  reg.test(str2) //false

^是几个有着多种用途的元字符之一,只有当它出现在一个字符集合里并且紧跟[之后,才能表现出"求非"的作用.如果是在一个字符集合外面并且位于一个模式的开头,^将匹配一个字符�串的开头.

  1. $匹配字符串的结尾

^的使用方法类似,我们用$来匹配字符串的结尾.用法如下:

  var str1 = 'I will persist until I succeed'
  var str2 = 'I will persist until I have  success'
  
  var reg = /s$/

  reg.test(str1) // -> false
  reg.test(str2) // -> true

接下来,我来说一种特殊的情况,就是当^$同时用于一个模式的时候.例:

var str1 = 'I will persist until I succeed'
var str2 ='I will'

var reg = /^I.*will$/
console.log( reg.test(str1)); // -> false
console.log( reg.test(str2)); // -> true 

我们可以看出当^$同时用于一个模式的时候, 其匹配的只是模式本身(即使字符串中含有与模式匹配的文本也不可以). 对此,我只能表示ㄟ( ▔, ▔ )ㄏ.

你是真滴皮

子表达式

子表达式是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当做一个独立元素来使用.子表达式必须用()括起来.

我们可以来看一个例子,用正则表达式来查找IP地址.IP地址的格式是以英文句号分割的四组数字,例如12.159.46.200.我们可以把它划分为两部分,一部分是形如xxx.的模式(IP地址的前3组便是这种模式),另一个部分是xxx的模式(IP地址的最后一组数字).

var  str = 'my ip is 12.159.46.200 ok.';
var reg = /(\d{1,3}\.){3}\d{1,3}/g

console.log(str.match(reg)) // ->['12.159.46.200']

回溯引用:前后一致匹配

回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式,它允许正则表达式模式引用前面的匹配结果,可将其想像成变量.

回溯引用只能用来引用模式里的子表达式(用()括起来的正则表达式片段),其匹配通常从1开始计数(\1\2,等等),在许多实现里,第0个(\0)可以用来代表整个正则表达式,同一个子表达式可以被引用任意多次。

我们来看一个例子,我们来html中的1级到6级标题标签.

var reg = /<h[1-6]>.*?<\/h[1-6]>/
var str1 = '<h1> welcome to my homeoage</h1>'
var str2 = '<h2> welcome to my homeoage</2>'
var str3 = '<h1> welcome to my homeoage</h2>'

console.log(reg.test(str1)) // -> true
console.log(reg.test(str2)) // -> true
console.log(reg.test(str3)) // -> true

我们会发现最后一个字符串并不不是我们预期的结果,但是它匹配成功了. 要解决这个问题, 就要用到我们上面所提到的回溯�引用.

var reg = /<(h[1-6])>.*?<\/\1>/
var str1 = '<h1> welcome to my homeoage</h1>'
var str2 = '<h2> welcome to my homeoage</h2>'
var str3 = '<h1> welcome to my homeoage</h2>'

console.log(reg.test(str1)) // -> true
console.log(reg.test(str2)) // -> true
console.log(reg.test(str3)) // -> false

我们可以看到,我们的模式不会匹配到第三个字符串了.

前后查找

有的时候,我们需要用正则表达式标记要匹配的文本的位置(而不仅仅是文本本身). 这样,便引出了前后查找(lookaround,对某一位置的前,后内容进行查找)的概念.

元字符 说明
?= 向前查找
?<= 向后查找

向前查找其实就是一个以?=开头的子表达式,而需要匹配的文本跟在=的后面.

例子:

var str1 = 'http://baidu.com' ;
var str2 = 'https://baidu.com' ;

// 我们想要查找文本中`:`前面的协议 .
var reg = /\w+(?=:)/;
console.log(reg.exec(str1)) // ->  http 
console.log(reg.exec(str2)) // ->  https

向后查找的操作符号是?<=,其功能与向前查找很类似.不过遗憾的是js的正则中没有向后查找.所以我只能从书上摘抄一个例子了:

文本:    
     ABC01 : $23.45
     HGG42 : $5.31

正则:
    (?<=\$)[0-9.]+

结果:
    23.45
    5.31

提示: 正则的前后查找是一种匹配但不消费的模式.

嵌入条件

正则表达式语言还有一种威力强大但不常用的功能----在表达式的内部嵌入条件处理功能.

电话号码: 
     123-456-7890
     (123)-456-7890

我们要想用一个正则来匹配这个两个电话号. 这种情况下,使用嵌入回溯引用条件,便可以很便捷的写出模式.

 正则:
 
 
  (\()?\d{3}(?(1)\)|-)\d{3}-\d{4}
  
 结果:
 
     123-456-7890
     
     (123)-456-7890
     

前后查找条件只在一个向前查找或者向后查找操作取得成功的情况下才允许一个表达式被使.

来看一个例子:

 文本: 
   11111
   22222
   33333-
   44444.4444

 正则: 
   \d{5}(?(?=-)-\d{4})

 结果: 
  11111
  22222
  44444.4444

结论:在正则表达式模式里可以嵌入条件,只有当条件得到(或者没得到)满足时,相应的表达式才会被执行.这种条件可以是一个回溯引用(含义是检查该),也可以是一个前后查找操作.

实战演练

 var reg = /^[a-z0-9]+([.-\\_]*[a-z0-9]+)*$/ 
 var str1 = 'abc'
 var str2 = 'ab.c'
 var str3 = 'abc.'
 var str4 = '.abc'
 var str5 = 'a.b.c'
console.log(reg.test(str1)) // -> true
console.log(reg.test(str2)) // -> true
console.log(reg.test(str3)) // -> false
console.log(reg.test(str4)) // -> false
console.log(reg.test(str5)) // -> true

刚好满足要求.是不是很nice.接下来我们就该写域名的正则了.

我们来看一下域名的要求:

其正则表达式如下:


 /^([a-z0-9]([-]?[a-z0-9])*.)+[a-z0-9]{2,}$/

我们来分析一下:

  1. 将正则划分成([a-z0-9]([-]?[a-z0-9])*.)+二级(或二级以上级别)域名 和[a-z0-9]{2,}顶级域名两部分.

  2. ([a-z0-9]([-]?[a-z0-9])*.)+中的的([a-z0-9]([-]?[a-z0-9])*.)类似于xxx.这样的格式,而+代表至少有一个子级别的域名,进一步分析这一部分.

  3. 其中的[a-z0-9]表示开头第一个字符只能是数字或者字母.

  4. ([-]?[a-z0-9])* 符合了-不能单独注册或连续使用,不能放在开头或结尾的要求.

  5. 最后的.则满足了域名之间用.分割的要求.

我们可以来验证一下:


var reg = /^([a-z0-9]([-]?[a-z0-9])*.)+[a-z0-9]{2,}$/
var str1 = 'baidu.com' 
var str2 = '.com' 
var str3 = 'baidu-.com' 
var str4 = 'bai--du.com' 
var str5 = 'b-a-i-d-u.com' 
var str6 = "yun.baidu.com"

console.log(reg.test(str1)) // -> true 

console.log(reg.test(str2))// -> false 

console.log(reg.test(str3))// -> false 

console.log(reg.test(str4))// -> false 

console.log(reg.test(str5))// -> true 

console.log(reg.test(str6))// -> true 

最终把它们和在一起,我们便可以得到邮箱的正则表达式:

/^[a-z0-9]+([.\-\\_]*[a-z0-9]+)*@([a-z0-9]([-]?[a-z0-9])*\.)+[a-z0-9]{2,}$/ 

用在线工具将改正则表达式其图形化.

邮箱正则

结束语

<<正则表达式必知必会>>的笔记到这里就告一段落了. 但其中内容却也只是正则表达式的冰山一角.在编程这一道路上我们还有很长的路要走,但愿你我能一直坚持下去,不求攀登到最高的顶峰,只求看那更多的风景.

后会有期
上一篇下一篇

猜你喜欢

热点阅读