目录
字符串字面量
任务描述
本关任务:使用字符串字面量定义正则表达式。
相关知识
在开发中,经常会有这样的需求:用一个式子表达有相同规律的一类字符串,比如国内的手机号码:必须是11
位数字,而且只能以13
、15
、17
、18
、19
开头。 这样的式子在JavaScript
中称为正则表达式。
正则表达式的定义
正则表达式:能够定义一类具有相同特征的字符串的式子。
在JavaScript
中,正则表达式有两种定义方法:
创建RegExp对象的实例
创建RegExp
对象的一个实例:
var myFirstPattern = new RegExp("s$");
上面等号右侧定义了一个正则表达式,并赋值给变量myFirstPattern
。
使用 // 符号定义
更简单的方法是直接用//
符号创建正则表达式:
var easyPattern = /s$/;
这里的easyPattern
和上面的myFirstPattern
含义一模一样。
匹配
上面的正则表达式表示的是这样一类字符串:以字母s
结尾的字符串,比如字符串apples
就符合这个要求,我们把符合要求叫做匹配。 需要注意的是,字符串和正则表达式匹配,指的是字符串的一部分(子串)和正则表达式匹配即可,比如/s/
匹配的是字母s
,一个单词如果含有字母s
,那么就和这个正则表达式匹配。 通过方法test()
可以检测字符串和正则表达式是否匹配,括号内的参数是待匹配的字符串,如果匹配返回true
,否则返回false
,如下:
var pattern = /s/;
pattern.test("s");//匹配,返回true
pattern.test("asa");//匹配,返回true
pattern.test("aa");//不匹配,返回false
使用正则表达式的关键是正确编写表达式,一个设计良好的正则表达式可以表示一类非常复杂的字符串。 为了实现这种复杂的表示,正则表达式提供了字符串字面量、字符类、重复、选择、分组、引用、指定匹配位置、修饰符等功能,这些将在本实训的剩余部分逐条讲述。
字符串字面量
正则表达式里面的数字和英文字母都是按照本来的含义匹配的,即aa
匹配的就是字符串aa
。123
匹配的就是数字123
。这些数字和字母称为字符串字面量。
var pattern = /aa/;
pattern.test("aa"); // true
pattern.test("aabb"); // true
pattern.test("abb") // false
一些特殊的符号,比如换行符、换页符,需要用斜杠加字母表示。比如\n
匹配的是换行符,下面列出一些常用的特殊符号:
| 字符 | 匹配 |
| ------------ | ------------ |
| \n | 换行符|
| \f | 换页符 |
| \t | 制表符 |
| \v | 垂直制表符 |
| \r | 回车符 |
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
-
定义正则表达式
pattern
; -
pattern
匹配字符串js
后面紧跟换行符的这样一类字符串;
function mainJs(a) {
a = a.replace(",","\n");
//请在此处编写代码
/********** Begin **********/
var pattern = /js\n/;
/********** End **********/
return pattern.test(a).toString();
}
字符类
任务描述
本关任务:定义两个正则表达式。
相关知识
字符类第一种表示
前一关阐述了字符串字面量的使用,一种更常见的需求是匹配一类字符:比如选择题的答案只能是ABCD
中的任何一个。正则表达式用字符类来实现,字符类被放在中括号之内。[ABCD]
表示一个字符类,它和A、B、C、D
中的任何一个都匹配。 还可以用[A-D]
表示上面的字符类,中间的-
表示A
到D
之间的所有字符。
var lowerCharPattern = /[a-z]/; // 匹配任意小写字母
var upperCharPattern = /[A-Z]/; // 匹配任意大写字母
var numberPattern = /[0-9]/; // 匹配任意数字
var mixPattern = /[a-zA-Z0-9]/; // 匹配大小写字母,数字
在中括号内最前面加上^
符号表示反向匹配:匹配和中括号内的正则表达式不匹配的所有字符,比如:
var notNumberPattern = /[^0-9]/;
notNumberPattern.test("123"); // false
notNumberPattern.test("ahc"); // true
[^0-9]
匹配的是任意非数字的字符。其中^
符号一定要在中括号内排在最前面。
字符类第二种表示
字符类还有一种较为简单的表示方法,比如\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
从上面可以看出,表示数字的正则表达式[0-9]
和表示小写字母的正则表达式[a-z]
直接连接起来,即表示数字后面紧跟小写字母。其他的字符串字面量或者字符类也可以用这种方式。
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
-
定义正则表达式
pattern1
和pattern2
; -
pattern1
用来匹配一个英文字母后面紧跟一个数字; -
pattern2
用来匹配字母A
后面紧跟一个非数字的字符;
function mainJs(a) {
//请在此处编写代码
/********** Begin **********/
var pattern1 = /[a-zA-Z][0-9]/;
var pattern2 = /[A][\D]/;
/********** End **********/
return pattern1.test(a).toString()+","+pattern2.test(a).toString();
}
重复
任务描述
本关任务:完成一个正则表达式的编写。
相关知识
通过前面的学习,我们可以编写一些简单正则表达式,比如两个连续的数字可以表示为[0-9][0-9]
,但是,这种方法有一个问题:比如手机号码的正则表达式,用11
个连续的[0-9]
写起来容易错,且不具有可读性。正则表达式用重复解决了这个问题。
重复
重复表示指定的字符或者字符串(本关可以简单理解为前面紧邻的字符)可以连续出现多次。比如匹配含有100
个字母a
的字符串,在这个字符串中,a
连续出现100
次,用正则表达式表示为:
var pattern = /a{100}/;//匹配100个连续的字母a组成的字符串
有多种表示重复的方法:
{a,b}
中的a
和b
都是数字,表示前面的字符至少出现a
次,最多出现b
次;var pattern = /at{1,2}/;//表示a后面最少一个t,最多两个t pattern.test("at");//true pattern.test("att");//true pattern.test("am");//false
{a,}
表示前面的字符至少出现a
次,最多不限制;var pattern = /[0-9]{4,}/;//匹配最少四个数字 pattern.test("1234");//true pattern.test("1");//false
{a}
表示前面的字符出现a
次;var pattern = /[a-z]{1}/;//匹配单个小写字母 pattern.test("r");//true pattern.test("12R");//false
?
,表示前面的字符出现一次或者不出现,等价于{0,1}
;var pattern = /A[0-9]?A/;//匹配两个A之间有0或1个数字 pattern.test("AA");//true pattern.test("A0A");//true pattern.test("A01A");//false
+
,表示前面的字符至少出现一次,等价于{1,}
;var pattern = /js+/; // 匹配j后面最少一个s pattern.test("jsjs"); // true pattern.test("java"); // false
*
,表示前面的字符至少出现0
次,等价于{0,}
;var pattern = /A[0-9]*B/; // 匹配A和B之间为空或者只有数字 pattern.test("AB"); // true pattern.test("A1B"); // true pattern.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}/;//匹配四个加号
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
-
定义正则表达式
pattern1
、pattern2
、pattern3
、pattern4
; -
pattern1
匹配这样一类字符串:?
至少出现一次; -
pattern2
匹配这样一类字符串:+
只出现三次; -
pattern3
匹配这样一类字符串:{}
最少出现一次,最多出现两次; -
pattern4
匹配这样一类字符串:\
出现一次或者不出现;
function mainJs(a) {
//请在此处编写代码
/********** Begin **********/
var pattern1 = /\?{1,}/;
var pattern2 = /\+{3,3}/;
var pattern3 = /\{\}{1,2}/;
var pattern4 = /\\{0,1}/;
/********** End **********/
return pattern1.test(a).toString()+","+pattern2.test(a).toString()+","+pattern3.test(a).toString()+","+pattern4.test(a).toString();
}
选择
任务描述
本关任务:完成身份证号,某地邮政编码以及三位数电话区号的正则表达式吧!
相关知识
考虑一下身份证号码的匹配:最后一位是数字或者X
,即二选一的关系,正则表达式使用选择实现这种匹配。
选择
选择使用符号|
来实现,比如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");//true
pattern1.test("Ab");//true,且匹配的是A
var pattern2 = /\d|\+/;//匹配数字或者+号
pattern2.test("1");//true
pattern2.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");//true
pattern.test("1ABC");//true
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
-
定义正则表达式
pattern1
、pattern2
和pattern3
; -
pattern1
用来匹配一段含有身份证号码的字符串,身份证号的规律是:17
位数字,加上1
位数字或者大写字母X
; -
pattern2
用来匹配一段含有某地的邮编的字符串,邮编的规律是:23
或者24
再加4
位数字; -
pattern3
用来匹配一段含有三位数电话区号的字符串,三位数电话区号只有10
个,它们分别是010
、020
、021
、022
、023
、024
、025
、027
、028
、029
;
function mainJs(a) {
//请在此处编写代码
/********** Begin **********/
var pattern1 = /[0-9]{17}([0-9]|X)/;
var pattern2 = /(23|24)[0-9]{4,4}/;
var pattern3 = /(010|020|021|022|023|024|025|027|028|029)/;
/********** End **********/
return pattern1.test(a).toString()+","+pattern2.test(a).toString()+","+pattern3.test(a).toString();
}
分组
任务描述
本关任务:练习使用分组创建正则表达式。
相关知识
子表达式
我们把一个完整的正则表达式的一部分叫做子正则表达式,或者子表达式,比如:
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}/; // 匹配字符串hellohello
pattern.test("shellohellos"); // 返回true
pattern.test("helloo"); // 返回false
用括号分组后,可以像使用单独的项一样使用子表达式,即可以+
、?
等符号操作它:
var pattern1 = /((hello){2})+/; // hellohello至少出现一次
var pattern2 = /(he|she)?/; // he或者she出现一次或不出现
console.log(pattern1.test("hello")); // 输出false
console.log(pattern1.test("hellohello")); // 输出true
console.log(pattern2.test("he")); // 输出true
console.log(pattern2.test("it")); // 输出true
编程要求
本关的编程任务是补全右侧代码片段中begin
至end
中间的代码,具体要求如下:
-
创建正则表达式
pattern1
,匹配这样的字符串:?+
需要连续出现至少两次; -
创建正则表达式
pattern2
,匹配这样的字符串:数字+
问号+
数字,或者数字+
加号+
数字,比如1?1
、1+4
、asd0+3asdfa
;
function mainJs(a) {
//请在此处编写代码
/********** Begin **********/
var pattern1 = /(\?\+){2,}/;
var pattern2 = /[0-9](\?|\+)[0-9]/;
/********** End **********/
return pattern1.test(a).toString()+","+pattern2.test(a).toString();
}
引用
任务描述
本关任务:在创建正则表达式时使用引用。
相关知识
编号
什么时候需要用到引用? 比如需要匹配这样一类字符串:以数字开头,中间是若干个字母,以数字结尾,并且开头的数字和结尾的数字相同,这个时候用前面所有介绍过的方法都不可行,无法确保开头的数字和结尾的数字相同。 所谓引用,就是在后面使用和前面完全相同的子表达式。我们把所有的带圆括号的子表达式编个号,从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"); // 返回true
pattern.test("123eduude456"); // 返回true
pattern.test("abcdefg"); // 返回false
在上面的正则表达式pattern
里面,我们先引用第三个子表达式,表示此处的字符串必须和第三个子表达式相同,然后引用第二个子表达式,最后引用的是第一个子表达式,所以pattern
匹配一个回文串,即顺序读取和倒序读取结果相同的字符串。
编程要求
本关的编程任务是补全右侧代码片段中begin
至end
中间的代码,具体要求如下:
-
创建正则表达式
pattern1
,匹配这样的字符串:三个数字加一个非数字字符再加三个数字,字符两侧的数字相同,如123A123
、234,234
; -
创建正则表达式
pattern2
,匹配这样的字符串:大写字母+
数字+
大写字母+
数字+
大写字母+
数字,三个数字相同,如A2B2C2
、F9D9K9
;
function mainJs(a) {
//请在此处编写代码
/********** Begin **********/
var pattern1 = /([0-9])([0-9])([0-9])([^0-9])\1\2\3/;
var pattern2 = /([A-Z])([0-9])([A-Z])\2([A-Z])\2/;
/********** End **********/
return pattern1.test(a).toString()+","+pattern2.test(a).toString();
}
匹配位置
任务描述
本关任务:利用正则表达式匹配以单词js
开头的文本。
相关知识
考虑这样一种情况,判断一个字符串是否为合法的JavaScript
变量名,变量名必须以字母、$
或者_
开头,这个时候要用到正则表达式中指定匹配位置的功能。
指定匹配位置
^
用来匹配字符串的开头,比如:
var startPattern = /^[0-9]/; // 匹配以数字开头的字符串
console.log(startPattern.test("1aa")); // true
console.log(startPattern.test("a11")); // false
注意,^
符号在中括号的外面!不要与[^0-9]
混淆,后者匹配非数字字符。 $
用来匹配字符串的结尾,比如:
var endPattern = /ing$/; // 匹配以ing结尾的字符串
console.log(endPattern.test("playing")); // true
console.log(endPattern.test("going first")); // false
\b
用来匹配单词的边界,那么什么是单词的边界?
图片中蓝色的线指示了单词的边界,实际上就是英文字母和非英文字母(如空格符)之间的界限。
var boundaryPattern = /\bOK\b/; // 匹配单词OK
console.log(boundaryPattern.test("OK")); // true
console.log(boundaryPattern.test("rewa OK de")); // true
console.log(boundaryPattern.test("sa,OK")); // true
console.log(boundaryPattern.test("OKNot")); // false
简单来说,\b
表示英文字母和非英文字母之间的界限,这个界限不占用字符的位置。 \B
用来匹配非单词的边界,与上面的\b
相反。
var pattern = /\Bed/;//ed左侧不能是单词的边界
console.log(pattern.test("played"));//true
console.log(pattern.test("edu"));//false
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
-
定义正则表达式
pattern
; -
pattern
匹配以单词js
开头的一段文本:这段文本以js
开头,且这个js
是一个单词,比如js is good
、js's history
符合要求,而js2
、jsjs
不符合要求;
function mainJs(a) {
//请在此处编写代码
/********** Begin **********/
var pattern = /^js\b/;
/********** End **********/
return pattern.test(a).toString();
}
修饰符
任务描述
本关任务:找到文本中的所有指定的单词。
相关知识
修饰符
修饰符用来描述一些整体的匹配规则,有i
、g
、m
三种。
修饰符需要放在//
符号之后,如果通过新建RegExp
对象定义正则表达式,则修饰符作为第二个参数。比如:
var pattern1 = /^java/m;
var pattern2 = new RegExp("^java","m");
不区分大小写
i
表示整个的匹配过程中不考虑单词的大小写。如:
var pattern = /^edU/i;
console.log(pattern.test("edu")); // 输出true
console.log(pattern.test("Edu")); // 输出true
console.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
匹配。
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
-
定义正则表达式
pattern
; -
pattern
能够匹配一段英文中所有的单词shell
,不区分大小写,该段英文在一行中; -
所谓单词,指前后紧跟的字符都不是英文字母的字符串;
function mainJs(a) {
//请在此处编写代码
/********** Begin **********/
var pattern = /\bshell\b/ig;
/********** End **********/
return a.match(pattern);
}
正则表达式的使用
任务描述
本关任务:对成绩单进行数据的清洗。
相关知识
本关将介绍字符串中有关正则表达式的方法。
字符串比较常用的方法有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"));//部分替换,输出aAb2c3
console.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
。
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
-
一段成绩单是类似这样的字符串:
Sivan:99,Kathy:100,Eray:8,
,即:人名+
冒号+
成绩+
逗号,成绩范围是0
到100
,人名是英文; -
使用
replace()
方法删掉其中的分数,保留其他所有字符串,比如上面的最终结果应该是Sivan:,Kathy:,Eray:,
;
function mainJs(a) {
//请在此处编写代码
/********** Begin **********/
var pattern = /[0-9]/g;
return a.replace(pattern, "");
/********** End **********/
}