
JS 中的正则表达式
正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于 JavaScript 的 RegExp 方法中,例如 test() 和 exec(),以及字符串的 match()、replace()、search() 和 split() 方法。
正则表达式是什么
正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于 JavaScript 的 RegExp
方法中,例如 test()
和 exec()
,以及字符串的 match()
、replace()
、search()
和 split()
方法。
创建正则表达式
正则表达式可以通过两种方式在 JavaScript 中创建:
- 字面量:使用两个斜杠包围的模式。javascript
const regex = /ab+c/
- 构造函数:使用
RegExp
对象的构造函数。javascriptconst regex = new RegExp('ab+c')
常用的正则表达式语法元素
- 锚点
^
匹配输入字符串的开始位置。$
匹配输入字符串的结束位置。
- 字符类
.
匹配除换行符以外的任何单字符。\d
匹配一个数字字符。等价于[0-9]
。\D
匹配一个非数字字符。等价于[^0-9]
。\w
匹配字母、数字、下划线。等价于[A-Za-z0-9_]
。\W
匹配非字母、数字、下划线。\s
匹配任何空白字符,包括空格、制表符、换行符等。\S
匹配任何非空白字符。[abc]
匹配任何一个指定字符(在此例中为 'a'、'b' 或 'c')。[^abc]
匹配任何不在指定集合中的字符。
- 量词
*
匹配前面的表达式 0 次或更多次。+
匹配前面的表达式 1 次或更多次。?
匹配前面的表达式 0 次或 1 次。{n}
精确匹配 n 次。{n,}
至少匹配 n 次。{n,m}
最少匹配 n 次且最多 m 次。
- 位置和断言
\b
匹配一个单词边界。\B
匹配非单词边界。(?=...)
正向前瞻,匹配的字符串后面必须紧跟着...
。(?!...)
负向前瞻,匹配的字符串后面不能紧跟着...
。
- 分组和引用
(xyz)
匹配并捕获括号内的表达式,也可以通过\1
、\2
等引用。(?:xyz)
只匹配但不捕获,不分配组号。
- 选择
|
匹配两个或多个分支选择的任意一个。
综合示例
假设我们需要从文本中找出所有格式为 (XXX) XXX-XXXX
的美国电话号码:
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}
分别匹配电话号码中的三位和四位数字。
常用场景
切分字符串
用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
'a b c'.split(' ') // ['a', 'b', '', '', 'c']
嗯,无法识别连续的空格,用正则表达式试试:
'a b c'.split(/\s+/) // ['a', 'b', 'c']
无论多少个空格都可以正常分割。加入,
试试:
'a,b, c d'.split(/[\s,]+/) // ['a', 'b', 'c', 'd']
再加入;
试试:
'a,b;; c d'.split(/[\s,;]+/) // ['a', 'b', 'c', 'd']
如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。
分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()
表示的就是要提取的分组(Group)。比如:
^(\d{3})-(\d{3,8})$
分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
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
。
提取子串非常有用。来看一个更凶残的例子:
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']
这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
const re = /^(0[1-9]|1[0-2]|\d)-(0[1-9]|1\d|2\d|3[01]|\d)$/
对于 '2-30'
, '4-31'
这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
贪婪匹配
需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0
:
const re = /^(\d+)(0*)$/
re.exec('102300') // ['102300', '102300', '']
由于 \d+
采用贪婪匹配,直接把后面的 0
全部匹配了,结果 0*
只能匹配空字符串了。
我们想要的是,前面的 ^(\d+)
部分匹配 1023,后面的 (0*)
匹配 00。
必须让 \d+
采用非贪婪匹配(也就是尽可能少匹配),才能把后面的 0
匹配出来,加个 ?
就可以让 \d+
采用非贪婪匹配:
const re = /^(\d+?)(0*)$/
re.exec('102300') // ['102300', '1023', '00']
全局搜索
JavaScript 的正则表达式还有几个特殊的标志,最常用的是 g
,表示全局匹配:
const r1 = /test/g
// 等价于:
const r2 = new RegExp('test', 'g')
全局匹配可以多次执行 exec()
方法来搜索一个匹配的字符串。当我们指定 g
标志后,每次运行 exec()
,正则表达式本身会更新 lastIndex
属性,表示上次匹配到的最后索引:
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
标志,表示执行多行匹配。