JavaScript 正则表达式
学习笔记
标签(空格分隔): 基础 JavaScript
正则表达式是用于匹配字符串中字符组合的模式。在javascript中,正则表达式也是对象。这些模式被用于
RegExp的exec、test方法,以及String的match、replace、search和split方法。
一、创建一个正则表达式
两种方法构建正则表达式:
- 使用一个正则表达式字面来那个,其由包含在斜杠之间的模式组成。在加载脚本后,正则表达式字面值提供正则表达式的编译。当正则表达式保持不变时,使用此方法可获得更好的性能。
const regex = /ab+c/;
const regex_2 = /^[a-zA-Z]+[0-9]*\W?_$/gi;
- 调用
RegExp对象的构造函数。使用构造函数提供正则表达式的运行时编译。使用构造函数,当你知道正则表达式模式将会改变,或者你不知道模式,并从另一个来源,如用户输入。
let regex_3 = new RegExp(\'ab+c\');
let regex_4 = new RegExp(/^[a-zA-Z]+[0-9]*\W?_$/, \'gi\');
let regex_5 = new RegExp(\'^[a-zA-Z]+[0-9]*\\W?_$\', \'gi\');
二、编写一个正则表达式的模式
一个正则表达式模式是由简单的字符所构成的,比如/ab/,或者是简单和特殊字符的组合,比如/ab*c/或/Chapter (\d+)\.\d*/。或者用到了括号,它在正则表达式中可以被用做是一个记忆设备。这一部分的正则所匹配的字符将会被记住,在后面可以被利用。
1. 使用简单的模式
简单的模式是由你找到的字符直接匹配所构成的。比如/abc/这个模式就匹配了字符串中仅仅字符\'abc\'同时出现并按照这个顺序。在\'Hi, do u know ur abc\'s?\'和\'The latest artplane designs evolved from slabcraft.\'都能匹配成功。在上面两个实例中,匹配的是子字符串\'abc\'。在字符串\'Grab crab\'中将不会被匹配,因为它不包含任何的\'abc\'子字符串。
2. 使用特殊字符
当你需要搜索一个比直接匹配需要更多条件的匹配时,比如寻找一个或多个\'b\'后面跟了零个或者多个\'b\'(*的意思就是前面一项出现了零个或者多个),而且后面跟着\'c\'的任何字符串\'cbbabbbbcdebc\'中,这个模式匹配了子字符串\'abbbbc\'。
正则表达式中的特殊字符
| 字符 | 含义 |
|---|---|
\ |
转义 匹配将依照下列规则: 在非特殊字符之前的反斜杠表示下一个字符是特殊的,不能从字面上解释。例如,没有\'\'的 \'b\'匹配小写\'b\',而加了\表示匹配一个字符边界。反斜杠也可以将其后的特殊字符,转义为字面量。例如,模式 /a*/表示会匹配0个或者多个a;而模式/a\*/表示匹配\'a*\'这样的字符串。使用 new RegExp(\'pattern\')时需要将\进行转义,因为\在字符串里也是一个转义字符。 |
^ |
匹配输入的开始。 如果多行标志被设置为true,那么也匹配换行符后紧跟的位置。 例如, /^A/并不会匹配\'an A\'中的A,但是会匹配\'An E\'中的A。当\'^\'作为第一个字符出现在一个字符集合模式时,它将会有不同的含义。 |
$ |
匹配输入的结束。匹配行或字符串的结尾。 如果多行标志被设置为true,那么也匹配换行符前的位置。 例如, /t$/不会匹配\'eater\'中的\'t\',会匹配\'eat\'中的\'t\'。 |
* |
匹配前一个表达式0次或多次。等价于{0,}。 例如, /bo*/会匹配\'A ghost boooooed\'中的\'booooo\'和\'A bird warbled\'中的\'b\',但是在\'A goat grunted\'中将不会匹配。 |
+ |
匹配前面一个表达式1次或者多次。等价于{1,}。 例如,在 /a+/匹配了\'candy\'中的\'a\',和在\'caaaaandy\'中的所有\'a\'。 |
? |
匹配前面一个表达式0次或1次。等价于{0,1}。 例如, /e?le?/匹配\'angel\'中的\'el\',\'angle\'中的\'le\',\'oslo\'中的\'l\'。如果紧跟在任何量词、+?或{}的后面,将会使量词变为非贪婪的(匹配尽量少的字符),和缺省使用的贪婪模式正好相反。 例如,\'123abc\'应用 /\d+/将会返回\'123\',如果使用/\d+?/,就只会匹配到\'1\'。还可以运用于先行断言,比如本表的 x(?=y)和x(?!y)条目中所述。 |
. |
(小数点)匹配除换行符之外的任何单个字符。 例如, /.n/匹配\'nay, an apple is on the tree\'中的\'an\'和\'on\',不会匹配\'nay\'。 |
(x) |
匹配\'x\'并且记住匹配项,就像下面的例子展示的那样,括号被称为捕获括号 模式 /(foo) (bar) \1 \2/中的\'(foo)\'和\'(bar)\'匹配并记住字符串\'foo bar foo bar\'中的前两个单词。模式中的\1和\2匹配字符串的后两个单词。注意使用\1、\2、\n是用在正则表达式的匹配环节。在正则表达式的替换环节,则要使用像$1、$2、$n的语法,例如,\'bar foo\'.replace(/(...) (...)/, \'$2 $1\')。 |
(?:x) |
匹配\'x\'但是不记住匹配项。这种叫做非捕获括号,使得你能够定义为与正则表达式运算符一起使用的子表达式。 来看示例表达式 /(?:foo){1,2}/。如果表达式是/foo{1,2}/,{1,2}将只对\'foo\'的最后一个字符\'o\'生效。如果使用非捕获括号,则{1,2}会匹配整个\'foo\'单词。 |
x(?=y) |
匹配\'x\'仅仅当\'x\'后面跟着\'y\'。这种叫做正向肯定查找。 例如, /Jack(?=Sprat)/会匹配到\'Jack\'仅仅当它后面跟着\'Sprat\'。/Jack(?=Sprat|Frost)/匹配\'Jack\'仅当它后面跟着\'Sprat\'或者是\'Frost\'。但是\'Sprat\'和\'Frost\'都不是匹配结果的一部分。 |
x(?!y) |
匹配\'x\'仅当\'x\'后面不跟着\'y\',这叫做正向否定查找。 例如, /\d+(?!\.)/匹配一个数字仅仅当这个数字后面没有跟小数点的时候。正则表达式/\d+(?!\.)/.exec(\'3.141\')匹配\'141\'而不是\'3.141\'。 |
x|y |
匹配x或者y。 例如,/green|red/匹配\'green apple\'中的\'green\'和\'red apple\'中的\'red\'。 |
{n} |
n是一个正整数,匹配了前面一个字符刚好发生了n次。 比如, /a{2}/不会匹配\'candy\'中的\'a\',但是会匹配\'caandy\'中所有的a,以及\'caaandy\'中的前两个\'a\'。 |
{n,m} |
n和m都是整数。匹配前面的字符至少n次,最多m次。如果n或者m的值是0,这个值被忽略。 例如, /a{1,3}/并不匹配\'cndy\'中的任意字符,匹配\'candy\'中的a,匹配\'caandy\'中的前两个a,匹配\'caaaaaandy\'中的前3个a。 |
[xyz] |
一个字符集合。匹配方括号中的任意字符,包括转义序列。 你可以用短横线( -)来指定一个字符范围。对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。例如, [abcd]和[a-d]是一样的,他们都匹配\'brisket\'中的\'b\',也都匹配\'city\'中的\'c\'。/[a-z.]+/和/[\w.]+/都匹配\'test.i.ng\'中的所有字符。 |
[^xyz] |
一个反向字符集。也就是说,它匹配任何没有包含在方括号中的字符。你可以使用短横线(-)来指定一个字符范围。任何普通字符在这里都起作用。例如, [^abc]和[^a-c]是一样的。他们匹配\'brisket\'中的\'r\',也匹配\'chop\'中的\'h\'。 |
[\b] |
匹配一个退格(U+0008)(不要和\b混淆)。 |
\b |
匹配一个词的边界。一个词的边界就是一个词不被另外一个词跟随的位置或者不是另一个词汇字符前边的位置。注意一个匹配的词的边界并不包含在匹配内容中。换句话说,一个匹配的词的边界的内容长度是0。 例如, /\bm/匹配\'moon\'中的\'m\'。/oo\b/不匹配\'moon\'中的\'oo\',因为\'oo\'被一个词汇字符\'n\'紧跟着。/oon\b/匹配\'moon\'中的\'oon\',因为\'oon\'是这个字符串的结束部分。这样它没有被一个词汇字符紧跟着。/\w\b\w/将不能匹配任何字符串,因为一个单词中的字符永远也不可能被一个非词汇字符和一个词汇字符同时紧跟着。注意:javascript的正则表达式引擎将»特定的字符集定义为“字”字符。不在该集合中的任何字符都被认为是一个断词。这组字符相当有限:它只包括大写和小写的罗马字母,小数位数和下划线字符。不幸的是,重要的字符,例如\'é\'和\'ü\'被视为断词。 |
\B |
匹配一个非单词边界。它匹配一个前后字符都是相同类型的位置:都是字符或者都不是单词。一个字符串的开始和结尾都被认为是非单词。 例如, /\B../匹配\'noonday\'中的\'oo\',而/y\B/匹配\'possibly yesterday\'中的\'y\'。 |
\cX |
当X是处于A到Z之间的字符的时候,匹配字符串中的一个控制符。 例如, /\cM/匹配字符串中的control-M(U+000D)。 |
\d |
匹配一个数字。 等价于 [0-9]。例如, /\d/或者/[0-9]/匹配\'B2 is the suite number.\'中的\'2\'。 |
\D |
匹配一个非数字子字符。 等价于 [^0-9]。例如, /\D/或者/[^0-9]/匹配\'B2 is the suite number.\'中的\'B\'。 |
\f |
匹配一个换页符(U+000C)。 |
\n |
匹配一个换页符(U+000A)。 |
\r |
匹配一个换页符(U+000D)。 |
\s |
匹配一个空白字符,包括空格、制表符、换页符和换行符。 等价于 [\f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如, /\s\w*/匹配\'foo bar\'中的\' bar\'。 |
\S |
匹配一个非空白字符。 等价于 [^\f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如, /\S\w*/匹配\'foo bar\'中的\'foo\'。 |
\t |
匹配一个水平制表符(U+0009)。 |
\v |
匹配一个垂直制表符(U+000B)。 |
\w |
匹配一个单字字符(字母、数字、下划线)。 等价于 [A-Za-z0-9_]。例如, /\w/匹配\'apple\'中的\'a\',\'$5.28\'中的\'5\',\'3D.\'中的\'3\'。 |
\W |
匹配一个非单字字符。 等价于 [^A-Za-z0-9_]。例如, /\W/匹配\'50%\'中的\'%\'。 |
\n |
当n是一个正整数,一个返回引用到最后一个与有n插入的正值表达式(counting left parentheses)匹配的副字符串。 比如, /apple(,)\sorange\1/匹配\'apple, orange, cherry, peach\'中的\'apple,orange,\'。按照我的理解,n就是在此之前需要的捕获括号的下标(从1开始)。 |
\0 |
匹配NULL(U+0000)字符,不要在这后面跟其它小数,因为\0<digits>是一个八进制转义序列。 |
\xhh |
与代码hh匹配字符(两个十六进制数字)。 |
\uhhhh |
与代码hhhh匹配字符(四个十六进制数字)。 |
\u{hhhh} |
(仅当设置了u标志时)使用Unicode值hhhh匹配字符(十六进制数字)。 |
将用户输入转义为正则表达式中的一个字面字符串,可以通过简单的替换来实现:
function escapeRegExp(string) { return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, \'\\$&\'/); // $&表示整个被匹配的字符串 }
3. 使用插入语
任何正则表达式的插入语都会使这部分匹配的副字符串被记忆。一旦被记忆,这个副字符串就可以被调用于其它用途,如同使用括号的子字符串匹配中所述。
比如,/Chapter (\d+)\.\d*/解释了额外转义的和特殊的字符,并说明了这部分pattern应该被记忆。它精确地匹配后面跟着一个以上数字字符的字符\'Chapter\'(\d意为任何数字字符,+意为1次以上),跟着一个小数点(在这个字符中本身也是一个特殊字符,小数点前的\意味着这个pattern必须寻找字面字符\'.\'),跟着任何数字字符0次以上。(\d表示数字字符,*表示0次以上)。另外,插入语也用来记忆第一个匹配的数字字符。
此模式可以匹配字符串\'Open Chapter 4.3, paragraph 6\',并且\'4\'将会被记住。此模式并不能匹配\'Chapter 3 and 4\',因为在这个字符串中\'3\'后面没有点\'.\'。
let str_17 = \'Open Chapter 4.3, paragraph 6\';
console.log(/Chapter (\d+)\.\d*/.exec(str_17)); // ["Chapter 4.3", "4", index: 5, input: "Open Chapter 4.3, paragraph 6"]
括号中的\'?:\',这种模式匹配的子字符串将不会被记住。比如,(?:\d+)匹配一次或多次数字字符,但是不能记住匹配的字符。
三、使用正则表达式
正则表达式可以被用于RegExp的exec和test方法,以及String的match、replace、search和split方法。
使用正则表达式的方法
| 方法 | 描述 |
|---|---|
exec |
一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(为匹配到则返回null)。 |
test |
一个在字符串中测试是否匹配的RegExp方法,它返回true或false。 |
match |
一个在字符串中执行查找匹配的String方法,它返回一个数组或者在未匹配到时返回null。 |
search |
一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。 |
replace |
一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。 |
split |
一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String方法。 |
当你想要知道在一个字符串中的一个匹配是否被找到,你可以使用test或search方法;想得到更多的信息(但是比较慢)则可以使用exec或match方法。如果你使用exec或match方法并且匹配成功了,那么这些方法将返回一个数组并且更新相关的正则表达式对象的属性和预定义的正则表达式对象(详见下)。如果匹配失败,那么exec方法返回null。
例如使用exec方法在一个字符串中查找一个匹配。
const myRe = /d(b+)d/g;
let str_18 = \'cdbbdbsbz\';
let myArray = myRe.exec(str_18);
console.log(myArray); // ["dbbd", "bb", index: 1, input: "cdbbdbsbz"]
如果不需要访问正则表达式的属性,这个脚本通过另一个方法来创建:
let myArray_2 = /d(b+)d/g.exec(str_18);
console.log(myArray); // ["dbbd", "bb", index: 1, input: "cdbbdbsbz"]
如果想通过一个字符串构建正则表达式,那么这个脚本还有另外一种方法:
const myRe_2 = new RegExp(\'d(b+)d\', \'g\');
let myArray_3 = myRe_2.exec(str_18);
console.log(myArray_3);
// ["dbbd", "bb", index: 1, input: "cdbbdbsbz"]
// 0: "dbbd"
// 1: "bb"
// index: 1
// input: "cdbbdbsbz"
// length: 2
// __proto__: Array(0)
通过这些脚本,匹配成功后将返回一个数组并且更新正则表达式的属性。
正则表达式执行返回信息
| 对象 | 属性或索引 | 描述 | 在例子中对应的值 |
|---|---|---|---|
myArray |
匹配到的字符串和所有被记住的子字符串。 | ["dbbd", "bb"] |
|
index |
在输入的字符串中匹配到的以0开始的索引值。 | 1 |
|
input |
初始字符串。 | "cdbbdbsbz" |
|
[0] |
匹配到的所有字符串(并不是匹配后记住的字符串)。匹配到的最后一个字符索引。 | "dbbd" |
|
myRe |
lastIndex |
下一个匹配的索引值(这个属性只有在使用g参数时可用)。 | 5 |
source |
模式文本。在正则表达式创建时更细,不执行。 | d(b+)d |
在这个例子中如第二种形式所示,你可以使用一个正则表达式创建一个没有分配给变量的对象初始化容器。如果你这样做,每一次使用时都好比在使用一个新的正则表达式。所以如果使用这个未分配给变量的正则,将在随后不能访问这个正则表达式的属性:
let myArray_4 = /d(b+)d/g.exec(str_18);
console.log(\'The value of lastIndex is \' + /d(b+)d/g.lastIndex); // The value of lastIndex is 0
1. 使用括号的子字符串匹配
一个正则表达式使用括号,将导致响应的子匹配被记住。例如,/a(b)c/可以匹配字符串\'abc\',并且记得\'b\'。回调这些括号中匹配的子串,使用数组元素[1],......[n]。
使用括号匹配的子字符串的数量数无限的。返回的数组中保存所有被发现的子匹配。下面的例子说明了如何使用括号的子字符串匹配。
下面的脚本使用replace()方法来转换字符串中的单词,在匹配到的替换文本中,脚本使用替代的$1,$2表示第一个和第二个括号的子字符串匹配。
const re_11 = /(\w+)\s(\w+)/;
let str_19 = \'Jehorn Gu\';
let str_19_r = str_19.replace(re_11, \'$2, $1\');
console.log(str_19_r); // Gu, Jehorn
2. 通过标志进行高级搜索
正则表达式有四个可选参数进行全局和部分大小写搜索。这些参数既可以单独使用也可以一起使用在任何顺序和包含正则表达式的部分中。
| 标志 | 描述 |
|---|---|
g |
全局搜索。 |
i |
不区分大小写搜索。 |
m |
多行搜索。 |
y |
执行“粘性”搜索,匹配从目标字符串的当前位置开始,可以使用y标志。 |
包含一个标志的正则表达式,使用这个表达式:
var re = /pattern/flags;
或者
var re = new RegExp("pattern", "flags");
值得注意的是,标志时一个正则表达式的一部分,它们在接下来的时间将不能添加或删除。
例如,re = /\w+\s/g将创建一个查找一个或多个字符后有一个空格的正则表达式,或者组合起来像此要求的字符串。
const re_12 = /\w+\s/g;
let str_20 = \'fee fi fo fum\';
let myArray_5 = str_20.match(re_12);
console.log(myArray_5); // ["fee ", "fi ", "fo "]
m标志用于指定多行输入字符串应该被视为多个行。如果使用m标志,^和$匹配的开始或结束输入字符串中的每一行,而不是整个字符串的开始或结束。
四、例子
1. 改变输入字符串的顺序
以下例子解释了正则表达式的构成和string.split()以及string.replace()的用途。它会整理一个只有粗略格式的含有全名(名字首先出现)的输入字符串,这个字符串被空格、换行符和一个分号分隔。最终,它会颠倒名字顺序(姓氏首先出现)和list的类型。
// 姓名字符串包含多个空格和制表符
// 而且可能在姓氏和名字之间有多个空格
let names = \'Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ; Chris Hand \';
let output = [\'---------- Original String\n\', names + \'\n\'];
// 准备两个正则表达式和数组模型
// 将字符串分隔到数组元素中
// 正则: 可能空格 分号 空格
let pattern = /\s*;\s*/;
// 将字符串拆分成上面模式分隔的部分
// 将这些片段存储在名为nameList的数组中
let nameList = names.split(pattern);
// 新的正则: 一个或多个字符 空格 字符
// 使用圆括号来"记住"匹配的部分
// 记住的部分将在后面使用
pattern = /(\w+)\s+(\w)/;
// 用于存放被处理后名字的数组
let bySurnameList = [];
// 显示名称数组并将用逗号分隔的名字填充到新的数组
// 替换方法删除了所有匹配到的,并且替换为记住的字符串的第一部分
// 随后是逗号,最后是记住字符串的第二部分
output.push(\'---------- After Split by Regular Expression\');
let i, len;
for (i = 0, len = nameList.length; i < len; i++) {
output.push(nameList[i]);
bySurnameList[i] = nameList[i].replace(pattern, \'$2, $1\');
}
// 显示新数组
output.push(\'---------- Names Reversed\');
for (i = 0, len = bySurnameList.length; i < len; i++) {
output.push(bySurnameList[i]);
}
// 根据姓氏排序 然后显示排好序的数组
bySurnameList.sort();
output.push(\'---------- Sorted\');
for (i = 0, len = bySurnameList.length; i < len; i++) {
output.push(bySurnameList[i]);
}
output.push(\'---------- End\');
console.log(output.join(\'\n\'));
// join() 方法用于把数组中的所有元素放入一个字符串。
// 元素是通过指定的分隔符进行分隔的。
// arrayObject.join(separator);
控制台打印效果为:
2. 用特殊字符检验输入
在以下例子中,我们期望用户输入一个电话号码。当用户点击\'Submit\'按钮,我们的脚本开始检查这些数字是否合法。如果数字合法(匹配正则表达式所规定的字符序列),脚本显示一条感谢用户的信息并确认该数字。如果这串数字不合法,脚本提示用户电话号码不合法。
包含非捕获括号(?:这个正则表达式寻找三个数字字符\d{3}或者|一个左半括号\(跟着三位数字\d{3},跟着一个封闭括号\),(结束非捕获括号)),后跟着一格短横线或者左斜杠或小数点,随后跟着三个数字字符,当记忆字符([-\/\.])捕获并记住,后面跟着三位数字\d{3},再后面跟着记住的左斜杠、右斜杠或小数点\1,最后跟着四位数字。
当用户按下Enter设置RegExp.input,这些变化也能被激活。
<p>
Enter your phone number (with area code) and then click \'SUBMIT\'. <br>
The expected format is like ###-###-####.
</p>
<form action="javascript: void(0);">
<input type="text" id="phone">
<button type="submit" onclick="testInfo(document.getElementById(\'phone\'));">SUBMIT</button>
</form>
<script type="text/javascript">
const re_phone = /(?:\d{3}|\(\d{3}\))([-\/\.])\d{3}\1\d{4}/;
function testInfo(phoneInput) {
let OK = re_phone.exec(phoneInput.value),
OK_str;
console.log(OK);
OK_str = phoneInput.value + \' isn\\'t a phone number with area code!\';
if (OK) OK_str = \'Thanks, ur phone number is \' + OK[0];
window.alert(OK_str);
}
</script>
The end... Last updated by: Jehorn, Jan 01, 2018, 4:16 PM
附录 常用总结
- 匹配任意字符
/^[\s\S]*$/