JS 中的正则表达式

JS 中的正则表达式

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于 JavaScript 的 RegExp 方法中,例如 test() 和 exec(),以及字符串的 match()、replace()、search() 和 split() 方法。

1192字

正则表达式是什么

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于 JavaScript 的 RegExp 方法中,例如 test()exec(),以及字符串的 match()replace()search()split() 方法。

创建正则表达式

正则表达式可以通过两种方式在 JavaScript 中创建:

  1. 字面量:使用两个斜杠包围的模式。
    javascript
    const regex = /ab+c/
    
  2. 构造函数:使用 RegExp 对象的构造函数。
    javascript
    const regex = new RegExp('ab+c')
    

常用的正则表达式语法元素

  1. 锚点
    • ^ 匹配输入字符串的开始位置。
    • $ 匹配输入字符串的结束位置。
  2. 字符类
    • . 匹配除换行符以外的任何单字符。
    • \d 匹配一个数字字符。等价于 [0-9]
    • \D 匹配一个非数字字符。等价于 [^0-9]
    • \w 匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
    • \W 匹配非字母、数字、下划线。
    • \s 匹配任何空白字符,包括空格、制表符、换行符等。
    • \S 匹配任何非空白字符。
    • [abc] 匹配任何一个指定字符(在此例中为 'a'、'b' 或 'c')。
    • [^abc] 匹配任何不在指定集合中的字符。
  3. 量词
    • * 匹配前面的表达式 0 次或更多次。
    • + 匹配前面的表达式 1 次或更多次。
    • ? 匹配前面的表达式 0 次或 1 次。
    • {n} 精确匹配 n 次。
    • {n,} 至少匹配 n 次。
    • {n,m} 最少匹配 n 次且最多 m 次。
  4. 位置和断言
    • \b 匹配一个单词边界。
    • \B 匹配非单词边界。
    • (?=...) 正向前瞻,匹配的字符串后面必须紧跟着 ...
    • (?!...) 负向前瞻,匹配的字符串后面不能紧跟着 ...
  5. 分组和引用
    • (xyz) 匹配并捕获括号内的表达式,也可以通过 \1\2 等引用。
    • (?:xyz) 只匹配但不捕获,不分配组号。
  6. 选择
    • | 匹配两个或多个分支选择的任意一个。

综合示例

假设我们需要从文本中找出所有格式为 (XXX) XXX-XXXX 的美国电话号码:

javascript
const text = 'Contact numbers are (555) 123-4567 and (321) 987-6543.'
const phoneRegex = /\(\d{3}\) \d{3}-\d{4}/g
const matches = text.match(phoneRegex)
console.log(matches) // 输出: ["(555) 123-4567", "(321) 987-6543"]

在这个示例中,\(\d{3}\) 匹配三位数字加括号,空格是字面意义上的空格,\d{3}\d{4} 分别匹配电话号码中的三位和四位数字。

常用场景

切分字符串

用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:

js
'a b   c'.split(' ') // ['a', 'b', '', '', 'c']

嗯,无法识别连续的空格,用正则表达式试试:

js
'a b   c'.split(/\s+/) // ['a', 'b', 'c']

无论多少个空格都可以正常分割。加入,试试:

js
'a,b, c  d'.split(/[\s,]+/) // ['a', 'b', 'c', 'd']

再加入;试试:

js
'a,b;; c  d'.split(/[\s,;]+/) // ['a', 'b', 'c', 'd']

如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。

分组

除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:

^(\d{3})-(\d{3,8})$ 分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:

js
const re = /^(\d{3})-(\d{3,8})$/
re.exec('010-12345') // ['010-12345', '010', '12345']
re.exec('010 12345') // null

如果正则表达式中定义了组,就可以在RegExp对象上用exec()方法提取出子串来。

exec() 方法在匹配成功后,会返回一个 Array ,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。

exec() 方法在匹配失败时返回 null

提取子串非常有用。来看一个更凶残的例子:

js
const re
  = /^(0\d|1\d|2[0-3]|\d):(0\d|1\d|2\d|3\d|4\d|5\d|\d):(0\d|1\d|2\d|3\d|4\d|5\d|\d)$/
re.exec('19:05:30') // ['19:05:30', '19', '05', '30']

这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:

js
const re = /^(0[1-9]|1[0-2]|\d)-(0[1-9]|1\d|2\d|3[01]|\d)$/

对于 '2-30''4-31' 这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。

贪婪匹配

需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0

js
const re = /^(\d+)(0*)$/
re.exec('102300') // ['102300', '102300', '']

由于 \d+ 采用贪婪匹配,直接把后面的 0 全部匹配了,结果 0* 只能匹配空字符串了。

我们想要的是,前面的 ^(\d+) 部分匹配 1023,后面的 (0*) 匹配 00。

必须让 \d+ 采用非贪婪匹配(也就是尽可能少匹配),才能把后面的 0 匹配出来,加个 ? 就可以让 \d+ 采用非贪婪匹配:

js
const re = /^(\d+?)(0*)$/
re.exec('102300') // ['102300', '1023', '00']

全局搜索

JavaScript 的正则表达式还有几个特殊的标志,最常用的是 g ,表示全局匹配:

js
const r1 = /test/g
// 等价于:
const r2 = new RegExp('test', 'g')

全局匹配可以多次执行 exec() 方法来搜索一个匹配的字符串。当我们指定 g 标志后,每次运行 exec() ,正则表达式本身会更新 lastIndex 属性,表示上次匹配到的最后索引:

js
const s = 'JavaScript, VBScript, JScript and ECMAScript'
const re = /[a-zA-Z]+Script/g

// 使用全局匹配:
re.exec(s) // ['JavaScript']
re.lastIndex // 10

re.exec(s) // ['VBScript']
re.lastIndex // 20

re.exec(s) // ['JScript']
re.lastIndex // 29

re.exec(s) // ['ECMAScript']
re.lastIndex // 44

re.exec(s) // null,直到结束仍没有匹配到

全局匹配类似搜索,因此不能使用 /^...$/ ,那样只会 最多匹配一次

正则表达式还可以指定 i 标志,表示忽略大小写; m 标志,表示执行多行匹配。

评论0