JS中的正则表达式
本文摘自这学期在学校的线上 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] |
//表示数字后面紧跟着一个小写字母
var pattern = /[0-9][a-z]/;
pattern.test("1a");//true
pattern.test("11a");//true
pattern.test("a1");//false
重复
重复表示指定的字符或者字符串(本关可以简单理解为前面紧邻的字符)可以连续出现多次。比如匹配含有100
个字母a
的字符串,在这个字符串中,a
连续出现100
次,用正则表达式表示为:
var pattern = /a{100}/;//匹配100个连续的字母a组成的字符串
有多种表示重复的方法:
{a,b}
中的a
和b
都是数字,表示前面的字符至少出现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]?/;
subPattern
是pattern
的前面一部分,称为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
修饰符
修饰符用来描述一些整体的匹配规则,有i
、g
、m
三种。
修饰符需要放在//
符号之后,如果通过新建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
匹配的有1A
、2B
、3C
,这其中与子表达式$1
匹配的分别是1
、2
和3
,所以$1
会相继被替换为1
、2
和3
,然后再用1
、2
和3
去分别替换1A
、2B
、3C
。