由浅至深总结 JS 正则表达式

正则表达式

认真学习了正则表达式之后才理解到正则表达式的强大之处,于是在这里记录一下自己的学习过程,并由浅至深地总结一下。

创建正则表达式的方法

  1. 两个正斜杠
const regex = /dog/
  1. 使用 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)。其结果还有其他一些属性,例如 indexinput

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 属性中,默认情况下启用此标志。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值