正则表达式
认真学习了正则表达式之后才理解到正则表达式的强大之处,于是在这里记录一下自己的学习过程,并由浅至深地总结一下。
创建正则表达式的方法
- 两个正斜杠
const regex = /dog/
- 使用 RegExp 构造函数
cosnt regex = new RegExp('dog')
创建对象后,可以在对象上调用 test
方法,方法得到字符串后,如果模式匹配,则返回 true
regex.test('dog') // true
regex.test('hot-dog') // true
简单模式
像上面的示例,就是简单模式
特殊字符
除了寻找某个字符串的简单出现,还可以做更多的事情。一种方法是使用特殊字符。它们不会被解释为被搜索字符串的直接内容,但能够以通用的方式对其进行描述。
任何字符
它由一个点 .
表示,用来匹配住了换行符以外的任何单个字符
cosnt regex = /.og/
regex.test('fog') // true
regex.test('dog') // true
反斜杠
用来转义特殊字符
字符集
用方括号 [ ]
表示,用来匹配一个字符,该字符可能是括号中的任何字符
/[dfl]og/.test('dog') // true
/[dfl]og/.test('fog') // true
/[dfl]og/.test('log') // true
需要注意的是字符集内的特殊字符(如点 .
)不再特殊,因此在这里不再需要反斜杠。我们甚至可以进一步定义一些字符:
/[A-z]/.test('a')
/[A-z]/.test('z')
可以通过添加 ^
符号轻松获得否定字符集,它会匹配方括号中未包含的所有内容
/[^df]og/.test('dog') // false
/[^df]og/.test('log') // true
一个重要的注意事项:[A-z] 范围实际上将匹配多个字母。正如你在 ASCII 表上看到的那样,[A-z] 也将与符号[、 \、 ]、 ^、 _ 和 ` 相匹配,所以请谨慎使用 [A-Za-z],而是使用标志来忽略大小写。
多次重复
一个非常有用的功能是匹配某个表达式出现的确切字数。可以用花括号 ( )
来实现
例:检查字符串是否为有效的电话号码,以 +xx xxx xxx xxx
格式为例
function isPhoneNumber (number) {
return /\+[0-9]{2} [0-9]{3} [0-9]{3} [0-9]{3}/.test(number)
}
isPhoneNumber('+12 123 123 123') // true
isPhoneNumber('123212') // false
其中,在此处进行了一些自定义
(x)
完全匹配 x 次出现(x,)
至少匹配 x 次(x, y)
至少匹配 x 次且不超过 y 次
零个或多个重复
带有星号 *
的表达式可以匹配 0 次或更多次,实际上等效于 (0, )
例可以匹配任意数量字符的模式:/.*/
标志
标志是一种影响搜索的修饰符。如果用斜杠定义正则表达式的话,就在斜杠后添加它们。如果用 RegExp 构造函数,则将它们作为第二个参数。最重要的标志是:
i: 忽略大小写
使用这个标志,搜索时不区分大小写
/dog/i.test('dog') // true
new RegExp('dog', 'i').test('DoG') //true
g: 全局匹配
使用这个标志,所有匹配项都能够被找到。如果没有它,将会在找到第一个匹配项后停止。
String.prototype.replace
使用 g
标志对字符串进行全局搜索并替换
const lorem = 'lorem_ipsum_dolor_sit_amet'
lorem.replace('_', ' ') // 'lorem ipsum_dolor_sit_amet'
lorem.replace(/_/g, ' ') // 'lorem ipsum dolor sit amet'
定义重复的较短方法
一次或多个重复
使用加号 +
,表示该表达式可能匹配一次或多次。类似于星号,但必须至少匹配一次。等效于 {1, }
。
/1+23/.test('123') // true
/1+23/.test('111123') // true
/1+23/.test('23') // false
例子:检查一个字符串是否包含另一个子字符串,但是不以他结尾
function hasQuestionMarkBeforeEnd (string) {
return /\?.+/.test(string)
}
hasQuestionMarkBeforeEnd('Do you know regex yet?') // false
hasQuestionMarkBeforeEnd('Do you know regex yet? Yes, I do!') // true
此处问号 ?
是一个特殊字符,所以要转义
可选字符
综上所述,问号是一个特殊字符。使用它可以创建带有可选字符的模式,它相当于 {0, 1}
function wereFilesFound (string) {
return /[1-9][0-9]* files? found/.test(string)
}
wereFilesFound('0 files found') // false
wereFilesFound('no files found') // false
wereFilesFound('1 files found') // true
wereFilesFound('10 files found') // true
用较短的方法定义一组可能出现的字符
之前我们使用方括号 []
来定义一组可能出现的字符。在正则表达式中,可以参考一些实现的集合
字母数字字符
如果想要匹配所有字母和数字字符,则需要这样的模式:/[A-Za-z0-9_]/
。有点小蠢,不过有一种更短的方法:\w
需要注意的是,这不能匹配任何特定语言的字符
非字母数字字符
与上述模式相反:/^[A-Za-z0-9_]/
等价于 \W
。同样不能处理任何特定语言的字符
function isAlphanumberic (string) {
return /\W/.test.(string)
}
function isNotAlphanumberic (string) {
return /\W/.test(string)
}
isAlphanumberic('Ó') // false
isNotAlphanumberic('Ó') // true
处理数字
之前我们了解到要匹配任何数字,我们可以使用类似 [0-9]
的模式,还可以用 \d
。它能够匹配任何数字:
isItADigit(string) {
return /\d/.test(string)
}
isItADigit('5') // true
isItADigit('a') // false
处理空格
在字符串中,有几种类型的空格字符
- 空格
' '
- tab
'\t'
- 新行
'\n'
- 回车符
'\r'
要创建一个匹配所有情况的模式,需要类似这样的复杂内容:/[\t\n\r]/
。不过,有一种更简单的方法,它涉及使用 \s
function containWhitespace (string) {
return /\s/.test(string)
}
containWhitespace('Lorem ipsum') // true
containWhitespace('Lorem_ipsum') // false
另外,\S
可以匹配任何非空白字符
指定位置
到目前为止,只是在写单纯可以在字符串中进行匹配的模式。我们还可以指定为止使匹配更精确
插入符号
如果在模式的开头添加 ^
符号,则仅当被测试的字符串以该模式开头时,它才会匹配
/^dog/.test('dog and cat') // true
/^dog/.test('cat and dog') // false
请注意,插入符号用在方括号中时有另外的作用(取反),具体可以网上翻翻
美元符号
在模式的末尾添加一个美元符号,仅当它出现在字符串的末尾时,才会匹配:
/dog$/.test('dog and cat') // false
/dog$/.test('cat and dog') // true
结合两个标志
当模式以 ^
开头,并以 $
结尾,则仅当测试的字符串整体匹配时,它才会匹配
/success/.test('Unsuccessful operation') // true
/^success$/.test('Unsuccessful operation') // false
多行模式
我们知道可以将其他标志添加到模式中,其中之一是由字母 m
表示的多行标志,它改变了插入符号和美元符号的含义。在多行模式下,它们代表一行的开头和结尾,而不是整个字符串
const pets = `
dog
cat
parrot and other birds
`
/^dog$/m.test(pets) // true
/^cat$/m.test(pets) // true
/^parrot$/m.test(pets) // false
由于使用了多行标志,因此是测试了多个行,而不测试整个字符串。
高级一点的概念
到现在为止,我们已经介绍了许多正则表达式的功能,但是还有更多,这次我们将学习一些更高级的概念,例如搜索和覆盖 JavaScript 中 RegExp 对象的更多功能。
exec
这是一种执行搜索字符串中的匹配项的方法(类似于 test
方法),但是它返回的结果是数组(或 null)。其结果还有其他一些属性,例如 index
和 input
const string = 'fileName.png, fileName2.png, fileName3.png'
const regexp = /fileName[0-9]?.png/g
regexp.exec(string)
[
0: "fileName.png",
index: 0,
input: "fileName.png, fileName2.png, fileName3.png"
]
index
是匹配项的位置,input
是提供的字符串,需要注意的是,我们在这里使用的是 global
标志,所以我们可以通过多次调用 exec
在字符串中寻找多个匹配项。它将 RegExp
对象的 lastIndex
属性设置为一个数字,该数字指示搜索停止的位置
let resultArray
while ((resultArray = regexp.exec(string)) !== null) {
console.log(resultArray[0], regexp.lastIndex)
}
// fileName.png 12
// fileName.png 27
// fileName.png 42
正则表达式中的分组
使用正则表达式,不仅可以检查字符串是否匹配,还可以在忽略不必要字符的同时提取某些信息。可以使用带有圆括号的分组
function getDateFromString (dateString) {
const regexp = /([0-9]{2})-([0-9]{2})-([0-9]{4})/
const result = regexp.exec(dataString)
if (result) {
return {
day: result[1],
month: result[2],
year: result[3]
}
}
}
getDateFromString('14-05-2018')
{
day: '14',
month: '05',
year: '2018'
}
在这种情况下,我们提取了三组字符,而忽略了破折号,只需注意 result[0]
将是匹配的完整字符串
已经有一个处于第 4 阶段的命名组提案
嵌套分组
function getDateFromString (dateString) {
const regexp = /[0-9]{2}-[0-9]{2}-([0-9]{2}([0-9]{2}))/
const result = regexp.exec(dataString)
if (result) {
return {
year: result[1],
yearShort: result[2],
}
}
}
getDateFromString('14-05-2018')
{
year: '2018',
yearShort: '18'
}
在模式的 ([0-9]{2}([0-9]{2}))
部分中,我们将一组嵌套在另一组中。于是,我们得到了表示年份的短字符串
条件模式
还有另一个有用的功能,即OR
语句,可以用 |
字符实现
function doYearsMatch (firstDateString, secondDateString) {
const execResult = /[0-9]{2}-[0-9]{2}-([0-9]{4})/.exec(firstDateString)
if (execResult) {
const year = execResult[1]
const yearShort = year.substr(2, 4)
return RegExp(`[0-9]{2}-[0-9]{2}-(${year}|${yearShort})`).test(secondDateString)
}
}
doYearsMatch('14-05-2018', '12-02-2018') // true
doYearsMatch('12-05-2018', '24-04-18') // true
按照我们的模式,(${year}|${yearShort})
将会匹配年份,即使第二个年份以简短形式提供
全部捕获
与组一起工作时,还有一个特别有用的地方:(.*)
function getResolution (resolutionString) {
const execResult = /(.*) ?x ?(.*)/.exec(resolutionString)
if (execResult) {
return {
width: execResult[1],
height: execResult[2]
}
}
}
getResolution('1024x768')
{
width: '1024',
height: '768'
}
因为 ?
的存在,可以消除乘号左右空格的影响
粘性标志(sticky)
RegExp 对象有一个名为 lastIndex
的属性,当进行全局搜索(使用适当的标志)时,可以在正确的位置继续进行模式匹配。使用 ES6 中引入的粘性标志 y
,可以强制从某个索引开始搜索
function getDateFromString (dateString) {
const regexp = /([0-9]{2})-([0-9]{2})-([0-9]{4})/y
regexp.lastIndex = 14
const result = regexp.exec(dateString)
if (result) {
return {
day: result[1],
month: result[2],
year: result[3]
}
}
}
getDateFromString('Current date: 14-05-2018')
要记住的是,对字符串执行检查(例如使用
exec
)会更改lastIndex
属性,所以如果你希望它在多次粘性搜索之间保持不变,请不要忘记对其进行设置。如果模式匹配失败,则将lastIndex
设置为 0
- 注意:可以检查
RegExp
对象是否启用了标志
const regexp = /([0-9]{2})-([0-9]{2}-([0-9]{4))/y
regexp.lastIndex = 14
console.log(regexp.sticky) // true
其他标志也是如此,更多标志点这里
Unicode 标志
ES6 也带来了对 Unicode 的更好支持。添加 Unicode 标志 u
可以启用与 Unicode
相关的其他功能
例:
/\u{24}/u.test('$') // true
以上代码如果没有 u
标志将会无法工作,重要的是要知道它的影响不仅限于此,如果不带标志,也可以处理一些更特殊的 Unicode 字符
/😹/.test('😹') // true
但像下面这种复杂一点的情况就不行了
/a.b/.test('a😹b') // false
/a.b/u.test('a😹b') // true
/😹{2}/.test('😹😹') // false
/😹{2}/u.test('😹😹') // true
/[a😹b]/.test('😹') // false
/[😹🐶]/u.test('😹') // true
/^[^x]$/.test('😹') // false
/[^x]/.test('😹') // true
/^[^x]$/u.test('😹') // true
/^[ab😹]$/.test('😹') // false
/[ab😹]/.test('😹') // true
/^[ab😹]$/u.test('😹') // true
有趣的是,在 HTML 中 input 和 textarea 元素的 pattern 属性中,默认情况下启用此标志。