本文摘自这学期在学校的线上 educoder 平台学习的 js 课程,把正则表达式部分的讲义整理了出来。

正则表达式

var lowerCharPattern = /[a-z]/;//匹配任意小写字母
var upperCharPattern = /[A-Z]/;//匹配任意大写字母
var numberPattern = /[0-9]/;//匹配任意数字
var mixPattern = /[a-zA-Z0-9]/;//匹配大小写字母,数字

在中括号最前面加上^符号表示反向匹配:匹配和中括号内的正则表达式不匹配的所有字符,比如:

var notNumberPattern = /[^09]/;
notNumberPattern.test("123");//false
notNumberPattern.test("ahc");//true

字符类还有一种较为简单的表示方法,比如\d[0-9]的含义相同:表示任意的数字。下面用表格列出:

字符 匹配 等价于
\w 大小写字符或数字 [a-zA-Z0-9]
\W 非字母,非数字 [^a-zA-Z0-9]
\d 数字 [0-9]
\D 非数字 [^0-9]
  1. //表示数字后面紧跟着一个小写字母
  2. var pattern = /[0-9][a-z]/;
  3. pattern.test("1a");//true
  4. pattern.test("11a");//true
  5. pattern.test("a1");//false
重复

重复表示指定的字符或者字符串(本关可以简单理解为前面紧邻的字符)可以连续出现多次。比如匹配含有100个字母a的字符串,在这个字符串中,a连续出现100次,用正则表达式表示为:

var pattern = /a{100}/;//匹配100个连续的字母a组成的字符串

有多种表示重复的方法:

  • {a,b}中的ab都是数字,表示前面的字符至少出现a次,最多出现b次;
var pattern = /at{1,2}/;//表示a后面最少一个t,最多两个tpattern.test("at");//truepattern.test("att");//truepattern.test("am");//false
  • {a,}表示前面的字符至少出现a次,最多不限制;
var pattern = /[0-9]{4,}/;//匹配最少四个数字pattern.test("1234");//truepattern.test("1");//false
  • {a}表示前面的字符出现a次;
var pattern = /[a-z]{1}/;//匹配单个小写字母pattern.test("r");//truepattern.test("12R");//false
  • ?,表示前面的字符出现一次或者不出现,等价于{0,1}
var pattern = /A[0-9]?A/;//匹配两个A之间有0或1个数字pattern.test("AA");//truepattern.test("A0A");//truepattern.test("A01A");//false
  • +,表示前面的字符至少出现一次,等价于{1,}
var pattern = /js+/;//匹配j后面最少一个spattern.test("jsjs");//truepattern.test("java");//false
  • *,表示前面的字符至少出现0次,等价于{0,}
var pattern = /A[0-9]*B/;//匹配A和B之间为空或者只有数字pattern.test("AB");//truepattern.test("A1B");//truepattern.test("AaB");//false

需要特别注意的是,至少出现0次,就是说这个字符可以不出现,比如正则表达式[0-9]{0,}和字符串hello是匹配的,这里特别容易出错。

总结一下重复匹配的用法,如下:

表达式 匹配 等价于
{a,b} 至少出现a次,最多出现b次
{a,} 至少出现a次
{a} 出现a次 {a,a}
+ 最少出现一次 {1,}
? 出现一次或者不出现 {0,1}
* 至少出现0次 {0,}
转义

相信细心的读者已经发现了一个问题:对于?+等表示特殊含义的字符,如何实现字面量匹配呢?就是说如何匹配他们本来的含义呢?

JavaScript中,使用\实现特殊符号的普通化,又叫做转义:

var pattern1 = new RegExp("\?");//匹配一个问号var pattern2 = /\+{4}/;//匹配四个加号
选择

选择使用符号|来实现,比如0|1表示匹配0或者1\d|a表示匹配数字或者字母a

第一关我们讲过:所谓匹配指的是匹配字符串的某一个子串。但是涉及到选择的时候情况就会有些复杂,比如正则表达式[0-9]|[a-z]匹配的是字符串1ABCa中的子串1还是a

看起来似乎都可以。实际上JavaScript会先挑选左边的子正则表达式[0-9]进行匹配,匹配成功后立即结束,所以匹配上的子串是1

下面是一些选择的例子:

var pattern1 = /[ABCD]|[abcd]/;//匹配ABCD中任意一个或者abcd中任意一个pattern1.test("A");//truepattern1.test("Ab");//true,且匹配的是Avar pattern2 = /\d|\+/;//匹配数字或者+号pattern2.test("1");//truepattern2.test("+");//true
选择符号作用范围

关于选择,还有一个问题需要明确:|作用到整个式子中的,即整个的正则表达式会被选择符号一分为二,一个字符串和该表达式匹配,要么匹配左侧的整个子正则表达式,要么匹配右侧的整个子正则表达式,如:

var pattern = /0|1ABC/;pattern.test("0");//true,和左侧的整个子正则表达式0匹配pattern.test("1");//false,和右侧的整个子正则表达式1ABC不匹配

如果想要限制|符号的作用范围,需要将目标作用范围用圆括号包含在内,如:

var pattern = /(0|1)ABC/;//选择符号仅仅作用在0和1上,而不是像上面的例子一样作用在整个正则表达式中pattern.test("0");//false,注意这里和上面的例子不同pattern.test("0ABC");//truepattern.test("1ABC");//true
子表达式

我们把一个完整的正则表达式的一部分叫做子正则表达式,或者子表达式,比如:

var pattern = /[0-9]?a{12,15}/;var subPattern = /[0-9]?/;

subPatternpattern的前面一部分,称为pattern的子表达式。

通过分组获得子表达式,通过引用操作子表达式。

分组

前面讲过的正则表达式的写法中,重复只能作用在紧邻符号的前面一个字符上,比如:

var pattern = /hello{3}/;

pattern表达的意思是字母o必须重复三遍,而不是单词hello必须重复三遍。

如果要表达单词hello必须重复三遍的意思,我们需要用到分组。

同样,要改变选择符号|的作用范围,也必须用到分组。

分组的符号是(),括号中是单独的项构成的子表达式,将这些子表达式看成一个整体进行操作,比如:

var pattern = /(hello){2}/;//匹配字符串hellohellopattern.test("shellohellos");//返回truepattern.test("helloo");//返回false

用括号分组后,可以像使用单独的项一样使用子表达式,即可以+?等符号操作它:

var pattern1 = /((hello){2})+/;//hellohello至少出现一次var pattern2 = /(he|she)?/;//he或者she出现一次或不出现console.log(pattern1.test("hello"));//输出falseconsole.log(pattern1.test("hellohello"));//输出trueconsole.log(pattern2.test("he"));//输出trueconsole.log(pattern2.test("it"));//输出true
编号

什么时候需要用到引用?

比如需要匹配这样一类字符串:以数字开头,中间是若干个字母,以数字结尾,并且开头的数字和结尾的数字相同,这个时候用前面所有介绍过的方法都不可行,无法确保开头的数字和结尾的数字相同。

所谓引用,就是在后面使用和前面完全相同的子表达式。我们把所有的带圆括号的子表达式编个号,从1开始。比如:

var pattern = /(A|B)(\d{5})not([0-9])/;//匹配字母A或者B后面紧跟5个数字,后面再紧跟字符串not,后面再紧跟1个数字

其中(A|B)编号为1(\d{5})编号为2([0-9])编号为3,中间的not不在圆括号内,不参与编号。

一个小问题:如果子表达式里面嵌套另外一层子表达式,引用时如何编号?简单来说,以子表达式前面的左括号的个数为准:

var pattern = /(play(ed|ing)){1,}/;

ed|ing前面有两个左括号,所以编号为2

引用

后面可以用\1引用编号为1的子表达式,依次类推,比如:

var pattern = /(A|B)(\d{5})not([0-9])\1\2/;

pattern在最后引用了第一个和第二个子表达式。

注意: 这里的引用是对与子表达式匹配的字符串的引用,而不是简单的对子表达式的引用。例如:

var pattern = /([0-9])AA\1/;

pattern不等价于正则表达式([0-9])AA[0-9],而是指字符串AA后面的数字必须和前面的相同,即形如1AA1这样的字符串。

再看一个例子:

var pattern = /([a-z])([a-z])([a-z])\3\2\1/;pattern.test("abccba");//返回truepattern.test("123eduude456");//返回truepattern.test("abcdefg");//返回false

在上面的正则表达式pattern里面,我们先引用第三个子表达式,表示此处的字符串必须和第三个子表达式相同,然后引用第二个子表达式,最后引用的是第一个子表达式,所以pattern匹配一个回文串,即顺序读取和倒序读取结果相同的字符串。

考虑这样一种情况,判断一个字符串是否为合法的JavaScript变量名,变量名必须以字母、$或者_开头,这个时候要用到正则表达式中指定匹配位置的功能。

指定匹配位置

^用来匹配字符串的开头,比如:

var startPattern = /^[0-9]/;//匹配以数字开头的字符串console.log(startPattern.test("1aa"));//trueconsole.log(startPattern.test("a11"));//false

注意,^符号在中括号的外面!不要与[^0-9]混淆,后者匹配非数字字符。

$用来匹配字符串的结尾,比如:

var endPattern = /ing$/;//匹配以ing结尾的字符串console.log(endPattern.test("playing"));//trueconsole.log(endPattern.test("going first"));//false

\b用来匹配单词的边界,那么什么是单词的边界?

图片中蓝色的线指示了单词的边界,实际上就是英文字母和非英文字母(如空格符)之间的界限。

var boundaryPattern = /\bOK\b/;//匹配单词OKconsole.log(boundaryPattern.test("OK"));//trueconsole.log(boundaryPattern.test("rewa OK de"));//trueconsole.log(boundaryPattern.test("sa,OK"));//trueconsole.log(boundaryPattern.test("OKNot"));//false

简单来说,\b表示英文字母非英文字母之间的界限,这个界限不占用字符的位置。

\B用来匹配非单词的边界,与上面的\b相反。

var pattern = /\Bed/;//ed左侧不能是单词的边界console.log(pattern.test("played"));//trueconsole.log(pattern.test("edu"));//false
修饰符

修饰符用来描述一些整体的匹配规则,有igm三种。

修饰符需要放在//符号之后,如果通过新建RegExp对象定义正则表达式,则修饰符作为第二个参数。比如:

var pattern1 = /^java/m;var pattern2 = new RegExp("^java","m");
不区分大小写

i表示整个的匹配过程中不考虑单词的大小写。如:

var pattern = /^edU/i;console.log(pattern.test("edu"));//输出trueconsole.log(pattern.test("Edu"));//输出trueconsole.log(pattern.test("EDUCoder"));//输出true
全局匹配

g表示执行全局匹配,即找出所有满足匹配的子字符串。比如,已知match()函数返回由匹配结果组成的数组,如果没有匹配到返回null

不用g修饰时:

var pattern = /[a-z]/;//匹配小写字母console.log("a1b2c3".match(pattern));//输出["a", index: 0, input: "a1b2c3"]

显然,只匹配到了第一个小写字母a

g修饰时:

var pattern = /[a-z]/g;//全局匹配小写字母console.log("a1b2c3".match(pattern));//输出["a", "b", "c"]

三个小写字母都被匹配到了,这就是所谓的全局匹配。

多行模式

有的时候,需要匹配的字符串很长,分为很多行(即中间有换行符号)。

m在多行模式中执行匹配,即:符号^不仅匹配整个字符串的开头,还匹配每一行的开头,&不仅匹配整个字符串的结尾,还匹配每一行的结尾。

var pattern = /[0-9]$/m;//多行匹配以数字结尾的字符串var string = "1\nhello";//字符串在两行,中间的\n是换行符console.log(pattern.test(string));//输出true

如果没有m修饰,将会输出false,因为这种情况下$不会和换行符\n匹配。

本关将介绍字符串中有关正则表达式的方法。

字符串比较常用的方法有search()replace()match()split(),这些方法的调用者都是字符串对象,参数中都有正则表达式。

search(a)方法

参数:a为正则表达式。功能:返回字符串中与该正则表达式匹配的第一个子串的起始位置,无匹配返回-1

var pattern = /[0-9]/;var string = "a3b2c1";console.log(string.search(pattern));//输出1
split(a)方法

参数:a是字符串或者正则表达式;功能:以a为分隔符分隔原来的字符串;返回值:分割后形成的子字符串数组。

console.log("a1b2c3d".split(/[0-9]/));//以数字为分隔符,输出["a", "b", "c", "d"]
replace(a,b)方法

参数:a是正则表达式,b是字符串;功能:用b替换掉第一个a匹配的子串,如果a中有修饰符g,替换掉所有子串;返回值:被替换后的字符串。

var pattern1 = /[0-9]/;var pattern2 = /[0-9]/g;var string = "a1b2c3";console.log(string.replace(pattern1,"A"));//部分替换,输出aAb2c3console.log(string.replace(pattern2,"A"));//全部数字被替换,输出aAbAcA

复杂的情况:b还可以是子表达式$1$2等,$1等会先被替换为与它匹配的子串。比如:

var pattern = /([0-9])[A-Z]/g;var string = "1A,2B,3C,1";console.log(string.replace(pattern,"$1"));//输出1,2,3,1

上面的$1指的是与子表达式[0-9]匹配的子字符串,比如第一个匹配1A$1指的是1,第二个匹配2B$1指的是2,依次类推。

string中与pattern匹配的有1A2B3C,这其中与子表达式$1匹配的分别是123,所以$1会相继被替换为123,然后再用123去分别替换1A2B3C