正则表达式(regex)是一种有用的编程工具。它们是高效文本处理的关键。知道如何使用正则表达式解决问题对你作为一个开发者是有帮助的,可以提高你的工作效率。
在这篇文章中,你将了解到正则表达式的基本原理,正则表达式模式符号,如何解释一个简单的正则表达式模式,以及如何编写你自己的正则表达式模式。让我们开始吧!
什么是正则表达式?
正则表达式是允许你描述、匹配或解析文本的模式。使用正则表达式,你可以做一些事情,比如查找和替换文本,验证输入数据是否符合所需的格式,以及其他类似的事情。
这里有一个场景:你想验证用户在表格上输入的电话号码是否符合某种格式,例如,##-###-####(其中#代表一个数字)。解决这个问题的一个方法可以是:
function isPattern(userInput){
if(typeof userInput !== ‘string’ || userInput.length !== 12) {
return false;
}
for(let i=0; i<userInput.length; i++){
let c = userInput[i];
switch (i){
case 0: case 1: case 2: case 4: case 5: case 6: case 8: case 9: case 10: case 11:
if(c < 0 || c > 9)return false;
break;
case 3: case 7:
if(c !== '-') return false;
break;
}
}
return true;
}
另外,我们可以在这里使用一个正则表达式,像这样:
function isPattern(userInput){
return /^\d{3}-\d{3}-\d{4}$/.test(userInput)
}
注意我们是如何使用正则表达式重构代码的。很神奇吧? 这就是正则表达式的力量。
如何创建一个正则表达式
在JavaScript中,你可以用两种方法中的任何一种来创建一个正则表达式。
- 方法一:使用正则表达式字面。这包括一个用正斜线括起来的模式。你可以用或不用标志来写(我们很快就会看到标志的含义)。语法如下:
let regExpLiteral = /pattern/ //without flags
let regExpLiteral = /pattern/flags; //with flags
正斜线/…/ 表示我们正在创建一个正则表达式模式,就像你用引号“ ” 来创建一个字符串一样。
- 方法#2:使用正则表达式构造函数,语法如下:
let regConstructor = new RegExp(pattern [, flags])
这里,模式被括在引号中,与标志参数一样,是可选的。
那么,你什么时候使用这些模式呢?
当你在编写代码时知道正则表达式模式时,你应该使用regex字面。
另一方面,如果要动态地创建regex模式,请使用Regex构造函数。另外,regex构造函数可以让你使用模板字头来编写模式,但这在regex字头语法中是不可能的。
什么是正则表达式标志?
标志或修饰符是启用高级搜索功能的字符,包括不区分大小写和全局搜索。你可以单独或集体地使用它们。一些常用的标志是:
g是用于全局搜索,这意味着搜索在第一个匹配后不会返回。i用于不区分大小写的搜索,意思是无论大小写如何,都会出现一个匹配。m用于多行搜索。u用于Unicode搜索。
让我们看看一些使用这两种语法的正则表达式模式。
如何使用正则表达式字面
//syntax => /pattern/flags
let regExpStr = "Hello world! hello there";
let regExpLiteral = /Hello/gi
console.log(regExpStr.match(regExpLiteral))
//output=> ["Hello", 'hello']
请注意,如果我们没有用i 标记该模式,则只返回Hello 。
模式/Hello/ 是一个简单模式的例子。一个简单模式由必须在目标文本中出现的字符组成。要发生匹配,目标文本必须遵循与该模式相同的序列。
例如,如果你重新写上一个例子中的文本,并试图匹配它:
let regExpLiteral = /Hello/gi
let regExpStr = “oHell world, ohell there!”
console.log(regExpStr.match(regExpLiteral));
//output=> null
我们得到的结果是null,因为字符串中的字符没有按照模式中的规定出现。因此,一个字面的模式,如/hello/ ,意味着h后面是e,后面是l,后面是o,完全是这样的。
如何使用一个regex构造器
//syntax=> RegExp(pattern [,flags])
let regExpConst = new RegExp(“xyz”, ‘g’); //with flag -g
let str = ‘xyz xyz’;
console.log(str.match(regExpConst));
//output: [‘xyz’, ‘xyz’]
这里,模式xyz 是作为一个字符串传入的,与标志相同。同时,xyz 的两次出现都被匹配,因为我们传入了-g 标志。如果没有这个标志,将只返回第一个匹配结果。
我们还可以使用构造函数将动态创建的模式作为模板字数传入。比如说:
let pattern = prompt('Enter a pattern')
//suppose the user enters 'xyz'
let regExpConst = new RegExp(`${pattern}`, 'gi')
let str = 'xyz XYZ';
console.log(str.match(regExpConst))
//output: ['xyz', 'XYZ']
如何使用正则表达式的特殊字符
正则表达式中的特殊字符是一个具有保留意义的字符。使用特殊字符,你可以做得更多,而不仅仅是找到一个直接的匹配。
例如,如果你想在一个字符串中匹配一个可能出现或不出现一次或多次的字符,你可以用特殊字符来实现。这些字符适合于不同的子组,执行类似的功能。
让我们来看看每个子组和与之配套的字符。
锚点和边界
锚点是元字符,与它们所检查的文本行的开始和结束相匹配。你用它们来断言边界应该在哪里。
使用的两个字符是^ 和$ 。
^匹配一行的开始,并在该行的开始处锚定一个字词比如说:
let regexPattern = /^cat/;
console.log(regexPattern.test(‘cat and mouse’)); //output=> true
console.log(regexPattern.test(‘The cat and mouse’))
//output=>false because the line does not start with cat
//without the ^ in the pattern, the output will return true because we did not assert a boundary.
let regexPattern = /cat/;
console.log(regexPattern.test(The cat and mouse)); //output => true
$匹配一行的结尾,并在该行的结尾处锚定一个字词。比如说:"匹配一行的结尾,并在该行的结尾处锚定一个文字:
let regexPattern = /cat$/;
console.log(regexPattern.test(‘The mouse and the cat’));
//output=> true
console.log(regexPattern.test(‘The cat and mouse’));
//output => false
请注意,锚定字符^ 和$ 只匹配模式中的字符位置 ,而不是实际字符本身。
字界 是元字符,与一个词的开始和结束位置相匹配--一个字母数字字符的序列。你可以把它们看作是基于单词的^ 和$ 。你使用元字符b 和B 来断言一个单词的边界。
\b匹配一个词的开始或结束。该词是根据元字符的位置来匹配的。这里有一个例子:
//syntax 1: /\b.../ where .... represents a word.
//search for a word that begins with the pattern ward
let regexPattern = /\bward/gi;
let text = “backward Wardrobe Ward”;
console.log(text.match(regexPattern)); //output: ['Ward', 'Ward']
//syntax 2: /...\b/
//search for a word that ends with the pattern ward
let regexPattern = /ward\b/gi;
let text = “backward Wardrobe Ward”;
console.log(text.match(regexPattern)); //output: ['ward', 'Ward']
//syntax 3: /\b....\b/
//search for a stand-alone word that begins and end with the pattern ward
let regexPattern = /ward\b/gi;
let text = “backward Wardrobe Ward”;
console.log(text.match(regexPattern)); //output: ['ward']
\B是与 相反的。它匹配每一个位置 ,而不是。\b\b
其他元字符的简码。
除了我们已经看过的元字符之外,下面是一些最常用的元字符:
\d- 匹配任何十进制数字,是[0-9]的简写。\w- 匹配任何字母数字字符,可以是字母、数字或下划线。 是[A-Za-z0-9_]的简写。\w\s- 匹配任何空白字符。\D- 匹配任何非数字,与[^0-9]相同。\W- 匹配任何非字(即非字母数字)字符,是[^A-Za-z0-9_]的速记。\S- 匹配非白色空格字符。.- 匹配任何字符。
什么是字符类?
一个字符类是用来匹配特定位置上的几个字符中的任何一个。为了表示一个字符类,你可以使用方括号[] ,然后在方括号内列出你想匹配的字符。
让我们看一个例子:
//find and match a word with two alternative spellings
let regexPattern = /ambi[ea]nce/
console.log(regexPattern.test(ambience)) //output:true
console.log(regex.test(ambiance)) //output:true
//The regex pattern interprets as: find a followed by m, then b,
//then i, then either e or a, then n, then c, and then e.
什么是否定的字符类?
如果你在一个字符类中加入一个圆点符号,如[^...] ,它将匹配任何没有列在方括号内的字符,比如说:
let regexPattern = /[^bc]at/
console.log(regexPattern.test(bat)) //output:false
console.log(regexPattern.test(cat)) //output:false
console.log(regexPattern.test(mat)) //output:true
什么是范围?
连字符- ,在字符类内使用时表示范围。假设你想匹配一组数字,例如[0123456789],或一组字符,例如[abcdefg]。你可以像这样把它写成一个范围,分别是[0-9]和[a-g]。
什么是交替法?
交替是另一种你可以指定一组选项的方式。在这里,你使用管道字符| 来匹配几个子表达式中的任何一个。这些子表达式中的任何一个都被称为备选。
管道符号意味着 "或",所以它可以匹配一系列的选项。它允许你把子表达式组合起来作为备选方案。
例如,(x|y|z)a 将匹配xa 或ya ,或za 。为了限制交替的范围,你可以使用圆括号将备选方案组合在一起。
如果没有括号,x|y|za 将意味着x 或y 或za ,比如说:
let regexPattern = /(Bob|George)Clan/ig
console.log(regexPattern.test(‘Bob Clan’)); //output: true
console.log(regexPattern.test(‘George Clan’)); //output: true
什么是量词和贪心?
量词表示一个字符、一个字符类别或一组字符应在目标文本中出现多少次才会发生匹配。这里有一些特殊的量化指标。
+如果该字符至少出现一次,它将与任何被附加的字符相匹配,比如说:
let regexPattern = /hel+o/
console.log(regexPattern.test(‘helo’)) //output:true
console.log(regexPattern.test(‘hellllllllllo’)) //output:true
console.log(regexPattern.test(‘heo’)) //output: false
*与 "+"字符类似,但有一点不同。当你将*附加到一个字符时,意味着你想匹配该字符的任何数量,包括没有,下面是一个例子:
let regexPattern = /hel*o/
console.log(regexPattern.test(‘helo’)) //output:true
console.log(regexPattern.test(‘hellllo’)) //output: true
console.log(regexPattern.test(‘heo’)) //output:true
//here the * matches 0 or any number of 'l'
?暗示 "可选"。当你把它附加到一个字符时,它意味着该字符可能出现,也可能不出现,例如:
let regexPattern = /colou?r/
console.log(regexPattern.test(‘color’)) //output:true
console.log(regexPattern.test(‘colour’)) //output:true
//the ? after the character u makes u optional
{N},当附加到一个字符或字符类时,指定我们要多少个字符。例如:/\d{3}/表示匹配三个连续的数字。{N,M}被称为区间量词,用于 ,为可能的最小和最大匹配指定一个范围。例如: ,表示最少匹配3个连续数字,最多匹配6个连续数字。/\d{3, 6}/{N, }表示一个无限制的范围。例如, 表示匹配任何3个或更多的连续数字。/\d{3, }/
什么是Regex中的贪婪性?
所有的量词在默认情况下都是贪婪的。这意味着它们会尝试匹配所有可能的字符。
为了消除这种默认状态,使它们不贪婪,你可以在运算符上添加一个? ,如:+?,*?,{N}?,{N,M}?.....,等等。
什么是分组和反向引用?
我们之前研究了如何使用括号来限制交替的范围。
如果你想一次在多个字符上使用像+ 或* 这样的量词--比如说一个字符类或组,该怎么办?你可以在追加量词之前用小括号把它们归为一个整体,就像这个例子一样:
let regExp = /abc+(xyz+)+/i
console.log(regExp.test('abcxyzzzzXYZ')) //output=>true
以下是该模式的含义。第一个+匹配abc的c,第二个+匹配xyz的z,第三个+匹配子表达式xyz,如果序列重复,就会匹配。
反推法允许你匹配一个与正则表达式中先前匹配的模式相同的新模式。你也可以使用圆括号进行反向引用,因为它可以记住它所包围的先前匹配的子表达式(也就是捕获的组)。
然而,在一个正则表达式中,有可能有一个以上的捕获组。因此,为了反推任何一个被捕获的组,你可以用一个数字来标识括号。
假设你在一个正则表达式中有3个捕获组,你想反向引用其中任何一个。你可以使用\1,\2, 或\3, 来指代第一、第二或第三括号。为了给括号编号,你从左边开始数开放的括号。
我们来看看一些例子:
(x)匹配x并记住该匹配:
let regExp = /(abc)bar\1/ig;
//abc is backreferenced and is anchored at the same position as \1
console.log(regExp.test('abcbarAbc')) //Output:true
console.log(regExp.test('abcbar')) //output:false
(?:x) 匹配x,但不记得匹配的内容。另外, \n(其中n是一个数字)不会记住以前捕获的组,并将作为一个字词进行匹配。使用一个例子:
let regExp = /(?:abc)bar\1/ig
console.log(regExp.test('abcbarabc')) //output:false
console.log(regExp.test('abcbar\1')) //output:true
转义规则
如果你想让一个元字符在你的正则表达式中以字面形式出现,就必须用反斜杠来转义。通过在正则表达式中转义元字符,元字符就失去了它的特殊意义。
正则表达式方法
test() 方法
在本文中我们已经多次使用这个方法。test()方法将目标文本与regex模式进行比较,并相应返回一个布尔值。如果有一个匹配,它就返回真,否则就返回假:
let regExp = /abc/ig
console.log(regExp.test(‘abcdef’)) //output:true
console.log(regExp.test(‘bcadef’)) //output:false
exec() 方法
exec() 方法将目标文本与regex模式进行比较。如果有匹配的,它返回一个包含匹配内容的数组--否则它返回null。比如说:
let regExp = /abc/ig
console.log(regExp.exec(‘abcdef’))
//output:[“abc”]
console.log(regExp.exec(‘bcadef’))
//output:null
另外,还有一些接受正则表达式作为参数的字符串方法,如 [match()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match), [replace()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace), [replaceAll()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll), [matchAll()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll), [search()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search), , 和 [split()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split).
雷格编码示例
这里有一些例子来巩固我们在本文中学到的一些概念。
第一个例子,如何使用一个重码模式来匹配一个电子邮件地址:
let regexPattern = /^[(\w\d\W)+]+@[\w+]+\.[\w+]+$/gi
console.log(regexPattern.test(abcdef123@gmailcom)) //output:false=>missing dot
console.log(regexPattern.test(abcdef123gmail.)) //output:false=>missing end literal 'com'
console.log(regexPattern.test(abcdef123@gmail.com))
//output:true=> the input matches the pattern correctly
让我们来解释这个模式,下面是发生的情况:
/代表正则表达式模式的开始。^用字符类中的字符检查行的开始。[(\w\d\W)+ ]+至少匹配一次字符类中的任何单词、数字和非单词字符。请注意,在添加量词之前,小括号是用来分组字符的。这与这个[\w+\d+\W+]+相同。@匹配电子邮件格式中的@字样。[\w+]+匹配该类字符中的任何一个单词字符,至少一次。\.转移点,使其作为一个字面字符出现。[\w+]+$匹配该类中的任何单词字符。同时,这个字符类被固定在行的末端。/- 结束模式
好了,下一个例子:如何匹配一个格式为example.com 或www.example.com 的URL:
let pattern = /^[https?]+:\/\/((w{3}.)?[\w+]+)\.[\w+]+$/gi
console.log(pattern.test(‘https://www.example.com))
//output: true
console.log(pattern.test(‘http://example.com’))
//output: true
console.log(pattern.test(‘https://example’))
//output:false
让我们也来解释一下这个模式。下面是发生的情况:
/...../代表重码模式的开始和结束^断定该行的开始[https?]+匹配至少一次列出的字符,然而 ,使's'成为可选项。?:匹配一个字面的分号。\/\/转移两个正斜线。(w{3}.)?匹配3次的字符w和紧随其后的点。然而,这一组是可选的。[\w+]+匹配这个类别的字符至少一次。\.避开点[\w+]+$匹配本类中的任何一个单词字符。另外,这个字符类被固定在行尾。
总结
在这篇文章中,我们了解了正则表达式的基本原理。我们还解释了一些正则表达式模式,并通过一些例子进行了练习。
除了这篇文章之外,正则表达式还有更多的内容。为了帮助你学习更多关于正则表达式的知识,这里有一些你可以阅读的资源。