正则表达式

356 阅读7分钟

正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。

断言Assertions

定义:表示一个匹配在某些条件下发生。断言包含先行断言、后行断言和条件表达式。

断言的组成之一是边界。对于文本、词或模式,边界可以用来表明它们的起始或终止部分

边界类断言

字符含义
^匹配输入的开头。如果多行模式设为 true,^ 在换行符后也能立即匹配,比如 /^A/ 匹配不了 "an A" 里面的 "A",但是可以匹配 "An A" 里面第一个 "A"。
$匹配输入的结束。如果多行模式设为 true,^ 在换行符前也能立即匹配,比如 /t$/ 不能匹配 "eater" 中的 "t",但是可以匹配 "eat" 中的 "t"。
\b匹配一个单词的边界,这是一个字的字符前后没有另一个字的字符位置, 例如在字母和空格之间。需要注意的是匹配的单词边界不包括在匹配中。换句话说,匹配字边界的长度为零。 比如 (1) /\bm/ 在 "moon" 中匹配到 "m" ;(2) /oo\b/ 在 "moon" 中不会匹配到 "oo", 因为 "oo" 后面跟着 "n" 这个单词字符;(3) /oon\b/ 在 "moon" 中匹配 "oon", 因为 "oon" 是这个字符串的结尾, 因此后面没有单词字符;(4) /\w\b\w/ 将永远不会匹配任何东西,因为一个单词字符后面永远不会有非单词字符和单词字符。
\B匹配非单词边界。这是上一个字符和下一个字符属于同一类型的位置:要么两者都必须是单词,要么两者都必须是非单词,例如在两个字母之间或两个空格之间。字符串的开头和结尾被视为非单词。与匹配的词边界相同,匹配的非词边界也不包含在匹配中。例如,/\Bon/ 在 “at noon” 中匹配 “on” ,/ye\B/ 在 "possibly yesterday"中匹配"ye" 。

其他断言

字符含义
x(?=y)向前断言: x 被 y 跟随时匹配 x。例如,对于/Jack(?=Sprat)/,“Jack”在跟有“Sprat”的情况下才会得到匹配./Jack(?=SpratFrost)/ “Jack”后跟有“Sprat”或“Frost”的情况下才会得到匹配。不过, 匹配结果不包括“Sprat”或“Frost”。
x(?!y)向前否定断言: x 没有被 y 紧随时匹配 x。例如,对于/\d+(?!\。)/,数字后没有跟随小数点的情况下才会得到匹配。对于/\d+(?!.)/.exec(3.141),匹配‘141’而不是‘3’。
(?<=y)x向后断言: x 跟随 y 的情况下匹配 x。例如,对于/(?<=Jack)Sprat/,“Sprat”紧随“Jack”时才会得到匹配。对于/(?<=JackTom)Sprat,“Sprat”在紧随“Jack”或“Tom”的情况下才会得到匹配。不过,匹配结果中不包括“Jack”或“Tom”。
(?<!y)x向后否定断言: x 不跟随 y 时匹配 x。例如,对于/(?<!-)\d+/,数字不紧随-符号的情况下才会得到匹配。对于/(?<!-)\d+/.exec(3) ,“3”得到匹配。 而/(?<!-)\d+/.exec(-3)的结果无匹配,这是由于数字之前有-符号。

示例

// 使用 正则表达式边界修复错误字符串
buggyMultiline = `tey, ihe light-greon apple
tangs on ihe greon traa`;

// 1) 使用 ^ 修正字符串开始处和换行后的匹配.
buggyMultiline = buggyMultiline.replace(/^t/gim,'h');
console.log(1, buggyMultiline); // 修复 'tey'=>'hey'(字符串开始) , 'tangs'=>'hangs'(换行后)

// 2) 使用 $ 修正字符串结尾处的匹配.
buggyMultiline = buggyMultiline.replace(/aa$/gim,'ee.');
console.log(2, buggyMultiline); // 修复 'traa' => 'tree'.

// 3) 使用 \b 修正单词和空格边界上的字符.
buggyMultiline = buggyMultiline.replace(/\bi/gim,'t');
console.log(3, buggyMultiline); // 修复 'ihe' => 'the'  不影响 'light'.

// 4) 使用 \B 匹配实体边界内的字符.
fixedMultiline = buggyMultiline.replace(/\Bo/gim,'e');
console.log(4, fixedMultiline); // 修复  'greon'  不影响'on'.

字符类

字符类可以区分各种字符,例如区分字母和数字。

字符含义
.有下列含义之一:(1)匹配除行终止符之外的任何单个字符: \n, \r, \u2028 or \u2029. 例如, /.y/ 在“yes make my day”中匹配“my”和“ay”,而不是“yes”; (2)在字符集内,点失去了它的特殊意义,并与文字点匹配。需要注意的是,m multiline标志不会改变点的行为。因此,要跨多行匹配一个模式,可以使用字符集[^]—它将匹配任何字符,包括新行。ES2018 添加了 s "dotAll" 标志,它允许点也匹配行终止符。
\d匹配任何数字(阿拉伯数字)。 相当于 [0-9]. 例如, /\d/ 或 /[0-9]/ 匹配 “B2is the suite number”中的“2”。
\D匹配任何非数字(阿拉伯数字)的字符。相当于[^0-9]. 例如, /\D/ or /[^0-9]/ 在 "B2 is the suite number" 中 匹配 "B".
\w匹配基本拉丁字母中的任何字母数字字符,包括下划线。相当于 [A-Za-z0-9_]. 例如, /\w/ 在 "apple" 匹配 "a" , "5" in "$5.28", "3" in "3D" and "m" in "Émanuel".
\W匹配任何不是来自基本拉丁字母的单词字符。相当于 [^A-Za-z0-9_]. 例如, /\W/ or /[^A-Za-z0-9_]/ 匹配 "%" 在 "50%" 以及 "É" 在 "Émanuel" 中.
\s匹配单个空格字符,包括空格,制表符,换页符,换行符和其他Unicode空格。 等效于[\ f \ n \ r \ t \ v \ u00a0 \ u1680 \ u2000- \ u200a \ u2028 \ u2029 \ u202f \ u205f \ u3000 \ ufeff]。 例如,/ \ s \ w * /匹配“ foo bar”中的“ bar”。
\S匹配空格以外的单个字符。 等效于[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。 例如,/ \ S \ w * /匹配“ foo bar”中的“ foo”。
\t匹配水平制表符。
\r匹配回车符
\n匹配换行符
\v匹配垂直制表符。
\f匹配换页。
[\b]匹配一个退格键

示例

var randomData = "015 354 8787 687351 3512 8735";
var regexpFourDigits = /\b\d{4}\b/g;
// \b indicates a boundary (i.e. do not start matching in the middle of a word)
// \d{4} indicates a digit, four times
// \b indicates another boundary (i.e. do not end matching in the middle of a word)


console.table(randomData.match(regexpFourDigits));
// ['8787', '3512', '8735']

Groups and ranges

组和范围表示表达式字符的 组和范围

字符含义
xy匹配 "x" 或 "y" 任意一个字符。例如, /greenred/ 在 "green apple" 里匹配 "green",且在 "red apple" 里匹配 "red" 。
[xyz] 或者 [a-c]字符集,匹配任何一个包含的字符。您可以使用连字符来指定字符范围,但如果连字符显示为方括号中的第一个或最后一个字符,则它将被视为作为普通字符包含在字符集中的文字连字符。也可以在字符集中包含字符类。 例如, [abcd] 是与[a-d].一样的,它们会 在"brisket" 中匹配 "b",在 "chop" 中匹配 "c" .
[^xyz]或者 [^a-c]** 一个否定的或被补充的字符集**。也就是说,它匹配任何没有包含在括号中的字符。可以通过使用连字符来指定字符范围,但是如果连字符作为方括号中的第一个或最后一个字符出现,那么它将被视为作为普通字符包含在字符集中。例如,[^abc]和[^a-c]一样。它们最初匹配“bacon”中的“o”和“chop”中的“h”。
(x)捕获组: 匹配x并记住匹配项。例如,/(foo)/匹配并记住“foo bar”中的“foo”。正则表达式可以有多个捕获组。结果,匹配通常在数组中捕获的组,该数组的成员与捕获组中左括号的顺序相同。这通常只是捕获组本身的顺序。当捕获组被嵌套时,这一点非常重要。使用结果元素的索引 ([1], ..., [n]) 或从预定义的 RegExp 对象的属性 (1,...,1, ..., 9).
(?:x)非捕获组: 匹配 “x”,但不记得匹配。不能从结果数组的元素中收回匹配的子字符串([1], ..., [n]) 或从预定义的 RegExp 对象的属性 (1,...,1, ..., 9).

示例

var aliceExcerpt = "There was a long silence after this, and Alice could only hear whispers now and then.";
var regexpVowels = /[aeiouy]/g;
console.log("Number of vowels:", aliceExcerpt.match(regexpVowels).length);
// Number of vowels: 25

let personList = `First_Name: John, Last_Name: Doe
First_Name: Jane, Last_Name: Smith`;
let regexpNames =  /First_Name: (\w+), Last_Name: (\w+)/mg;
let match = regexpNames.exec(personList);
do {
  console.log(`Hello ${match[1]} ${match[2]}`); 
  // hello John Doe
  // hello Jane Smith
} while((match = regexpNames.exec(personList)) !== null);

量词

量词表示要匹配的字符或表达式的数量。

字符含义
x*将前面的项“x”匹配0次或更多次。例如,/bo*/匹配“A ghost booooed”中的“boooo”和“A bird warbled”中的“b”,但在“A goat grunt”中没有匹配。
x+将前一项“x”匹配1次或更多次。等价于{1,}。例如,/a+/匹配“candy”中的“a”和“caaaaaaandy”中的“a”。
x?将前面的项“x”匹配0或1次。例如,/ e ?勒?/匹配angel中的el和angle中的le。 如果立即在任何量词*、+、?或{}之后使用,则使量词是非贪婪的(匹配最小次数),而不是默认的贪婪的(匹配最大次数)。
x{n}其中“n”是一个正整数,与前一项“x”的n次匹配。例如,/a{2}/ 不匹配“candy”中的“a”,但它匹配“caandy”中的所有“a”,以及“caaandy”中的前两个“a”。
x{n,}其中,“n”是一个正整数,与前一项“x”至少匹配“n”次。例如,/a{2,}/不匹配“candy”中的“a”,但匹配“caandy”和“caaaaaaandy”中的所有a。
x{n,m}其中,“n”是0或一个正整数,“m”是一个正整数,而m > n至少与前一项“x”匹配,最多与“m”匹配。例如,/a{1,3}/不匹配“cndy”中的“a”,“candy”中的“a”,“caandy”中的两个“a”,以及“caaaaaaandy”中的前三个“a”。注意,当匹配“caaaaaaandy”时,匹配的是“aaa”,即使原始字符串中有更多的“a”。
x*? 或 x+? 或 x?? 或 x{n}? 或x{n,}? 或 x{n,m}?默认情况下,像 * 和 + 这样的量词是“贪婪的”,这意味着它们试图匹配尽可能多的字符串。?量词后面的字符使量词“非贪婪”:意思是它一旦找到匹配就会停止。例如,给定一个字符串“some <foo> <bar> new </bar> </foo> thing”: (1) /<.*>/ 匹配 "<foo> <bar> new </bar> </foo>" (2) /<.*?>/ 匹配 "<foo>"

示例

var singleLetterWord = /\b\w\b/g;
var notSoLongWord = /\b\w{1,6}\b/g;
var loooongWord = /\b\w{13,}\b/g;

var sentence = "Why do I have to learn multiplication table?";

console.table(sentence.match(singleLetterWord)); // ["I"]
console.table(sentence.match(notSoLongWord));    // [ "Why", "do", "I", "have", "to", "learn", "table" ]
console.table(sentence.match(loooongWord));      // ["multiplication"]可选可选字符

正则表达式修饰符

修饰符可用于大小写不敏感的更全局的搜索

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

使用正则表达式

正则表达式可以被用于 RegExp 的 exec 和 test 方法以及 String 的 match 、replace、search 和 split 方法。

方法描述
exec一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回 null)。
test一个在字符串中测试是否匹配的RegExp方法,它返回 true 或 false。
match一个在字符串中执行查找匹配的String方法,它返回一个数组,在未匹配到时会返回 null。
matchAll一个在字符串中执行查找所有匹配的String方法,它返回一个迭代器(iterator)。
search一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。
replace一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。
split一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 String 方法。

贪婪匹配和非贪婪匹配

这是正则匹配的两种模式

  • 贪婪匹配:尽可能匹配最长的字符串
  • 非贪婪匹配: 尽可能匹配最短的字符串

默认情况下匹配都是贪婪模式是从后往前匹配,最大长度的匹配, 惰性匹配就是在量词后面加个?从字符串的前面开始匹配,最小长度的匹配

使用贪婪模式还是非贪婪模式,这主要取决于我们的需求。但有一点,非贪婪模式的性能一定是高于贪婪模式的

示例

贪婪匹配

vars ="abbbaabbbaaabbb1234";
varre1=/.*bbb/g;//*是贪婪量词
re1.test(s);

这个匹配过程将从整个字符串开始:

re1.test("abbbaabbbaaabbb1234");//false ,则去掉最后一个字符4再继续
re1.test("abbbaabbbaaabbb123");//false ,则去掉最后一个字符3再继续
re1.test("abbbaabbbaaabbb12");//false ,则去掉最后一个字符2再继续
re1.test("abbbaabbbaaabbb1");//false ,则去掉最后一个字符1再继续
re1.test("abbbaabbbaaabbb");//true ,结束

非贪婪匹配

vars ="abbbaabbbaaabbb1234";
varre1=/.*?bbb/g;//*?是惰性量词
re1.test(s);

它的匹配过程如下:

re1.test("a");//false, 再加一个
re1.test("ab");//false, 再加一个
re1.test("abb");//false, 再加一个
re1.test("abbb");//true, 匹配了,保存这个结果,再从下一个开始
re1.test("a");//false, 再加一个
re1.test("aa");//false, 再加一个
re1.test("aab");//false, 再加一个
re1.test("aabb");//false, 再加一个
re1.test("aabbb");//true, 匹配了,保存这个结果,再从下一个开始