很多程序设计语言都实支持正则表达式的语法,在 JavaScript 中,正则表达式主要用于匹配位置或字符,以及String方法中使用的模式匹配。

基础语法

每个元字符在正则表达式中具有相对应的功能,如果匹配的字符是元字符的话,需要用转义字符\进行转义,如/\./匹配字符.

基本元字符

元字符/表达式 功能
. 匹配除换行符之外的任意单个字符
例如,/.n/将会匹配 “nay, an apple is on the tree” 中的 ‘an’
\ 转义字符
例如,模式 /a*/ 代表会匹配 0 个或者多个 a。相反,模式 /a\*/ 将 ‘*‘ 的特殊性移除,从而可以匹配像 “a*“ 这样的字符串。
| 逻辑或
例如,/green|red/匹配“green apple”中的‘green’和“red apple”中的‘red’
[] 定义一个字符集,匹配集合中的一个字符,集合中的元字符如:.\等都表示本身,无需转义,不过转义也是起作用的
例如,[abcd] 和[a-d]是一样的。他们都匹配”brisket”中得‘b’,也都匹配“city”中的‘c’。/[a-z.]+/ 和/[\w.]+/都匹配“test.i.ng”中的所有字符
[^] []表达式的反义表达式,匹配不包括在字符集中的任意一个字符
例如,[^abc] 和 [^a-c] 是一样的。他们匹配”brisket”中的‘r’,也匹配“chop”中的‘h’。
- 定义一个区间,如[a-z],区间的起点与终点在 ASCII 字符集里
(x) 匹配 ‘x’ 并且记住匹配项,括号被称为捕获括号
模式/(foo) (bar) \1 \2/中的’(foo)’和’(bar)’匹配并记住字符串 “foo bar foo bar” 中前两个单词。模式中的 \1 和 \2 匹配字符串的后两个单词。注意 \1、\2、\n 是用在正则表达式的匹配环节。在正则表达式的替换环节,则要使用像 $1、$2、$n 这样的语法,例如,’bar foo’.replace( /(…) (…)/, ‘$2 $1’ )。
(?:x) 匹配 ‘x’ 但是不记住匹配项,叫作非捕获括号
如果表达式是 /foo{1,2}/,{1,2}将只对 ‘foo’ 的最后一个字符 ‘o’ 生效。如果使用非捕获括号 /(?:foo){1,2}/,则{1,2}会匹配整个 ‘foo’ 单词。

数量元字符

元字符/表达式 功能
{n,m} 匹配表达式前面一个字符,重复至少 n 次,至多 m 次,{n,}省略 m 表示至少重复 n 次
例如,/a{1, 3}/ 并不匹配“cndy”中的任意字符,匹配“candy”中的 a,匹配“caandy”中的前两个 a,也匹配“caaaaaaandy”中的前三个 a
+ 匹配前面一个表达式出现一次或多次,相当于{1,}
例如,/a+/匹配了在 “candy” 中的 ‘a’,和在 “caaaaaaandy” 中所有的 ‘a’。
* 匹配前面一个表达式出现 0 次或多次,相当于{0,}
例如,/bo*/会匹配 “A ghost boooooed” 中的 ‘booooo’ 和 “A bird warbled” 中的 ‘b’,但是在 “A goat grunted” 中将不会匹配任何东西。
? 单独使用表示匹配前面一个表达式出现 0 次或 1 次,相当于{0,1}
例如,/e?le?/可以匹配“angel”中的’el’,和’angle’中的’le’以及’oslo’中的’l’
如果紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为非贪婪的(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)正好相反。

位置元字符

元字符/表达式 功能
^ 单独使用表示匹配表达式的起始位置
例如,/^A/并不会匹配“an A”中的“A”,但是会匹配“An E”中的“A”
$ 匹配表达式的结束位置
例如,/t$/并不会匹配“eater”中的“t”,但是会匹配“eat”中的“t”
\b 匹配单词边界,边界代表单词开始或结束的位置,单词边界具体指 \w([a-zA-Z0-9_]) 和 \W 之间的位置,包括 \w 和 ^ 以及 $ 之间的位置
例如,/\bm/匹配“moon”中的“m”;/oo\b/并不能匹配“moon”中的“oo”,因为“oo”不在单词的边界
\B \b元字符的反义字符,表示匹配不是单词开始或结束的位置
例如,/\B../匹配“possibly yesterday”中的“os”,而/y\B../匹配“possibly yesterday”中的“yes”
(?=x) 匹配字符 x 前面的位置,也称为零宽度正预测先行断言
例如,/Jack(?=Sprat|Frost)/匹配“Jack”后面跟着“Spart”或者“Frost”,但是“Spart”和“Frost”都不是结果的一部分
(?!x) 匹配不是字符 x 前面的位置,也称为零宽度负预测先行断言
例如,/\d+(?!\.)/匹配一个数字后面没有跟小数点的时候,/\d+(?!.)/.exec(‘3.14’)匹配“14”

有特殊含义的元字符

元字符/表达式 功能
\d 相当于[0-9],匹配一个 0 到 9 之间的数字
例如,/\d/匹配“B2”中的“B2”
\D \d的反义字符,相当于[^0-9],匹配一个非数字的字符
例如,/\D/匹配“B2”中的“B”
\w 相当于[0-9a-zA-Z_]加汉字,表示匹配数字、小写字母、大写字母、下划线和汉字中的任意一个字符
例如,/\w/匹配“$5.28”中的“5”
\W \w的反义字符,相当于[^0-9a-zA-Z_],表示匹配一个非单字字符
例如,/\W/匹配“50%” 中的“%”
\s 相当于[\t\v\n\r\f ],表示匹配一个空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符
例如,/\s\w*/匹配“foo bar.”中的“bar”
\S \s的反义字符,表示匹配一个非空表字符
例如,/\S\w*/匹配“foo bar.”中的“foo”
\u{hhhh} (仅当设置了 u 标志时) 使用 Unicode 值 hhhh 匹配字符 (十六进制数字)
例如,/\u{20BB7}/u.test(‘𠮷’)

标志字符

标志 描述
g 全局搜索
i 不区分大小写搜索
m 多行搜索
u 用来正确处理大于\uFFFF 的 Unicode 字符
y 执行“粘性”搜索,匹配从目标字符串的当前位置开始,可以使用 y 标志。

使用正则表达式

正则表达式可以被用于RegExpexectest方法,以及Stringmatchreplacesearchsplit方法。

test

RegExp 方法,接收一个字符串入参,如果正则表达是与入参字符串匹配返回 true,否则返回 false。

exec

RegExp 方法,接收一个字符串入参,返回一个存放匹配结果的数组(未匹配返回 null)。数组的第一项为匹配的子串,后面 n 项为 n 个捕获文本,最后两项是 index 和 input,index 表示匹配的子串在字符串中的索引,input 表示被解析的原始字符串。当正则表达式有g标志时,执行exec()得到匹配文本后,正则实例的lastIndex属性被设为匹配文本的下一个位置,因为可以重复执行exec遍历整个字符串匹配所有的文本,当没有匹配的文本时,返回null,lastIndex属性重置为 0;如果没有g标志,lastIndex执行完不会改变,值保持为0,即使重复执行,返回的结果总是相同的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var string = '2017.06.26'
var regexp = /\b(\d+)\b/g
console.log(regexp.exec(string))
// ["2017","2017",index: 0, input: "2017.06.26"]
console.log(regexp.lastIndex)
// 4
console.log(regexp.exec(string))
// ["06","06",index: 5, input: "2017.06.26"]
console.log(regexp.lastIndex)
// 7
console.log(regexp.exec(string))
// ["26","26",index: 8, input: "2017.06.26"]
console.log(regexp.lastIndex)
// 10
console.log(regexp.exec(string))
// null
console.log(regexp.lastIndex)
// 0

exec可以与while配合使用获取全部匹配的文本:

1
2
3
4
5
6
var string = '2017.06.26'
var regexp = /\b(\d+)\b/g
var result
while ((result = regexp.exec(string))) {
console.log(result)
}

String 方法,接收一个正则入参,返回参数匹配的子串的起始位置,不支持全局搜索,未匹配到结果返回-1。如果参数不是正则,会隐式的使用new RegExp(argument)进行类型转换

match

String 方法,接收一个正则入参,返回的内容由正则参数是否带有g标志决定,有g,返回一个由匹配结果组成的数组;无g,只做一次匹配,返回的数组中第一项是匹配的子串,其余项为正则捕获的文本,即 a[n]中存放的是$n 的内容,数组最后两项,index 和 input。

1
2
3
4
5
var string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
var regexp = /[A-E]/gi
var matchArray = string.match(regexp)
console.log(matchArray)
//["A", "B", "C", "D", "E", "a", "b", "c", "d", "e"]
1
2
3
4
5
6
7
8
9
10
11
var string = 'For more information, see Chapter 3.4.5.1'
var regexp = /see (chapter \d(\.\d)*)/i
var found = string.match(regexp)
console.log(found)
// ["see Chapter 3.4.5.1", "Chapter 3.4.5.1", ".1", index: 22, input: "For more information, see Chapter 3.4.5.1"]
// 0:"see Chapter 3.4.5.1"
// 1:"Chapter 3.4.5.1"
// 2:".1"
// index:22
// input:"For more information, see Chapter 3.4.5.1"
// length:3

replace

String 方法,检索与替换,接收两个参数,第一个:正则或文本;第二个:用来替换的字符或函数,字符可以用以下特殊的字符序列:

字符序列 替换文本
$$ 插入一个“$”
$& 插入匹配的子串
`$`` 插入当前匹配子串左边的内容
$' 插入当前匹配子串右边的内容
$n 匹配第 n 个捕获组的子字符串, 其中 n 等于 0 ~ 9。 例如, $1 是匹配第一个捕获组的子字符串, $2 是匹配第二个捕获组的子字符串, 以此类推。 如果正则表达式中没有定义捕获组, 则使用空字符串
$nn 匹配第 nn 个捕获组的子字符串, 其中 n 等于 01 ~ 99。
1
2
3
4
var regexp = /(\w+)\s(\w+)/
var string = 'John Smith'
var newString = string.replace(regexp, '$2, $1')
//"Smith, John"

第二个参数如果是函数,函数的入参如下,返回替换后的文本:

参数名 描述
match 匹配的子串(对应于上面提到到 $& )
p1,p2... 捕获组的匹配项(对应于上面提到的 $n )
offset 模式的匹配项在字符串中的位子
string 原始字符串
1
2
3
4
5
function replacer(match, p1, p2, p3, offset, string) {
return [p1, p2, p3].join('-')
}
var newString = 'abc123%$#'.replace(/([^\d]*)(\d*)(\W*)/, replacer)
//"abc-123-%$#"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function htmlEscape(text) {
return text.replace(/[<>"&]/g, function(match, pos, originalText) {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '&':
return '&'
case '"':
return '"'
}
})
}
console.log(htmlEscape('

Hello world!

'
))

split

String 方法,接收两个参数,第一个:正则,如果是空字符串则会将原字符串中的每个字符以数组形式返回;第二个:返回的数组长度,返回一个数组。注意:用捕获括号的时候会将匹配结果也包含在返回的数组中。

1
2
3
4
5
6
7
8
9
var myString = 'Hello 1 word. Sentence number 2.'
var splits = myString.split(/\d/)

console.log(splits)
// [ "Hello ", " word. Sentence number ", "." ]

splits = myString.split(/(\d)/)
console.log(splits)
// [ "Hello ", "1", " word. Sentence number ", "2", "." ]

正则的匹配

贪婪与非贪婪模式

贪婪模式在整个表达式匹配成功的前提下,尽可能多的匹配;非贪婪模式在整个表达式匹配成功的前提下,尽可能少的匹配。

JS 正则表达式引擎默认使用贪婪模式:

1
2
3
var regex = /\d{2,5}/g
var string = '123 1234 12345 123456'
console.log(string.match(regex)) // ["123", "1234", "12345", "12345"]

正则表达式加?可以开启非贪婪模式:

1
2
3
var regex = /\d{2,5}?/g
var string = '123 1234 12345 123456'
console.log(string.match(regex)) // ["12", "12", "34", "12", "34", "12", "34", "56"]

位置匹配

位置存在于字符与字符之间,如下图所示:

正则表达式中进行位置匹配有如下几种方式:

  1. ^$匹配一个字符串的开始和结束的位置。
1
'hello'.replace(/^|$/g, '#') // #hello#
  1. \b\B匹配单词边界和非单词边界。
1
'hello word [js]_reg.exp-01'.replace(/\b/g, '#') // #hello# #word# [#js#]#_reg#.#exp#-#01#
  1. (?=p)(?!p)匹配p前面的位置和不是p前面的位置。
1
2
'hello'.replace(/(?=l)/g, '#') // he#l#lo
'hello'.replace(/(?!l)/g, '#') // #h#ell#o#

分组和分支结构

分组用一对()将内容包裹起来表示;分支结构由|分隔。

假设匹配下面的字符串:

1
2
I love JavaScript
I love Regular Expression

正则表达式可以写成:/^I love (JavaScript|Regular Expression)$/

反向引用

假设我们需要匹配1999-09-191999.09.191999/09/19三种日期格式。首先可能想到的正则表达式是:

1
var reg = /\d{4}[./-]\d{2}[./-]\d{2}/

上面的表达式有个问题是1999-09.19也被匹配到了。可以使用反向引用解决这个问题。反向引用可以在匹配节点捕获分组的内容,使用\n表示,n代表第几个分组。重写上面的表达式:

1
var reg = /\d{4}([./-])\d{2}\1\d{2}/ // \1 引用第一个分组中捕获到的内容

常用正则

通过使用可视化 正则分析网站 帮助理解正则表达式。

  1. 最多保留 2 位小数的数字
1
var reg = /^([1-9]\d*|0)(.d{1,2})?$/
  1. 电话号码
1
var reg = /(+86)?1\d{10}/
  1. 身份证
1
var reg = /^(\d{15}|\d{17}([xX]|\d))$/
  1. 千分位逗号分隔
1
2
3
'123123213'.replace(/(?!^)(?=(\d{3})+$)/g, ',')
// 或
'123123213'.replace(/(\d)(?=(\d{3})+$)/g, '$1,')
  1. 修改日期格式 yyyy-mm-dd =》 mm/dd/yyyy
1
'1999-09-19'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1')
  1. 驼峰转蛇形
1
str.replace(/([A-Z])/g, '-$1').toLowerCase()
  1. 获取 url 上的 query 值 https://www.example.com?name1=value1&name2=value2
1
2
3
4
5
6
function query(name) {
//["?name1=value1", "value1", index: 0, input: "?name1=value1&name2=value2"]
let match = RegExp(`[?&]${name}=([^&]*)`).exec(window.location.search)
return match && decodeURIComponent(match[1].replace(/\+/g, ' ')) // url中+号表示空格
}
query('name1')
  1. trim
1
2
3
function trim(string) {
return string.replace(/^\s*|\s*$/g, '')
}

正则表达式的基本使用方法介绍到这里。

参考文献

  1. JS 的正则表达式 from 掘金
  2. JavaScript 正则进阶之路 from Github
  3. JavaScript 正则进阶之路 from MDN
  4. 正则的扩展 from 阮一峰