简介
通过使用正则表达式,可以:
- 测试字符串内的模式。例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这成为数据验证。
- 替换文本。可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。
- 基于模式匹配从字符串中提取子字符串。可以查找文档内或输入域内特定的文本。
发展历史
正则表达式的"祖先"可以一直上溯至对人类神经系统如何工作的早期研究。Warren McCulloch 和 Walter Pitts 这两位神经生理学家研究出一种数学方式来描述这些神经网络。
1956 年, 一位叫 Stephen Kleene 的数学家在 McCulloch 和 Pitts 早期工作的基础上,发表了一篇标题为"神经网事件的表示法"的论文,引入了正则表达式的概念。正则表达式就是用来描述他称为"正则集的代数"的表达式,因此采用"正则表达式"这个术语。
随后,发现可以将这一工作应用于使用 Ken Thompson 的计算搜索算法的一些早期研究,Ken Thompson 是 Unix 的主要发明人。正则表达式的第一个实用应用程序就是 Unix 中的 qed 编辑器。
如他们所说,剩下的就是众所周知的历史了。从那时起直至现在正则表达式都是基于文本的编辑器和搜索工具中的一个重要部分。
语法
正则表达式是由普通字符(例如字符a到z)以及特殊字符(成为“元字符”)组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串 。正则表达式作为一个模板,将某个字符模式与搜索的字符串进行匹配。
普通字符
普通字符包括没有显示指定为元字符的所有可打印和不可打印字符。包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
非打印字符
非打印字符也可以是正则表达式的组成部分。下表是非打印字符的转义序列:
字符 | 描述 |
---|---|
\cx | 匹配由x指明的控制字符。例如,\cM匹配一个 Control-M 或回车符。x的值必须为 A-Z 或 a-z 之一,否则,将 c 视为一个原义的 'c' 字符 |
\f | 匹配一个换页符。等价于 \x0c 和 \cL |
\n | 匹配一个换行符。等价于 \x0a 和 \cJ |
\r | 匹配一个回车符。等价于 \x0d 和 \cM |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [\f\n\r\t\v] |
\S | 匹配任何非空白字符。等价于 [^\f\n\r\t\v] |
\t | 匹配一个制表符。等价于 \x09 和 \cl |
\v | 匹配一个垂直制表符。等价于 \x0b 和 \cK |
特殊字符
下表是正则表达式中的特殊字符:
特殊字符 | 描述 |
---|---|
$ | 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 '\n' 或 '\r' |
() | 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用 |
* | 匹配前面的子表达式零次或多次 |
+ | 匹配前面的子表达式一次或多次 |
. | 匹配除换行符 \n 之外的任何单字符。 |
[ | 标记一个中括号表达式的开始 |
? | 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符 |
\ | 将下一个字符标记为特殊字符、或原义字符、或向后引用、或八进制转义符 |
^ | 匹配输入字符串的开始位置,在方括号表达式中使用,表示不接受该字符集合 |
{ | 标记限定符表达式的开始 |
| | 指明两项之间的一个选择 |
限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配:
字符 | 描述 |
---|---|
* | 匹配前面的子表达式零次或多次。等价于 {0,} |
+ | 匹配前面的子表达式一次或多次。等价于 {1,} |
? | 匹配前面的子表达式零次或一次。等价于 {0,1} |
{n} | n 是一个非负整数。匹配确定的 n 次 |
{n,} | n 是一个非负整数。至少匹配 n 次 |
{n,m} | m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次最多匹配 m 次 |
定位符
定位符能够将正则表达式固定到行首或行尾。
正则表达式的定位符有以下四个
字符 | 描述 |
---|---|
^ | 匹配输入字符串开始的位置 |
$ | 匹配输入字符串结尾的位置 |
\b | 匹配一个字边界,即字与空格间的位置 |
\B | 非字边界匹配 |
注意:不能将限定符与定位点一起使用。由于在紧靠换行或者字边界的前面或后面不能有一个以上位置,因此不允许诸如 ^* 之类的表达式。
字边界是单词和空格之间的位置。非字边界是任何其他位置。
/\bCha/ // 匹配 Cha 开头的字符串
/ter\b/ // 匹配 ter 结尾的字符串
/\Bapt/ // 匹配含有 apt 子串的字符串 并且 apt不位于字符串边界(对于 \B 非字边界运算符,位置并不重要,因为匹配不关心究竟是单词的开头还是结尾。)
选择
用圆括号将所有选择项括起来,相邻的选择项之间用 | 分隔。使用圆括号时,相关的匹配会被缓存,可用 ?: 放在第一个选项前来消除这种副作用。
?: 是非捕获元之一,还有两个非捕获元是 ?= 和 ?!,前者为正向预查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串,后者为负向预查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。
反向引用
对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从1开始,最多可存储99个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
可以使用非捕获元字符 '?:'、'?=' 或 '?!' 来重写捕获,忽略对相关匹配的保存。
运算符优先级
正则表达式从左到右进行计算,并遵循优先级顺序。相同优先级的从左到右进行运算,不同优先级的运算先高后低。下表从最高到最低说明了各种正则表达式运算符的优先级顺序:
运算符 | 描述 |
---|---|
\ | 转义符 |
(), (?:), (?=), [] | 圆括号和方括号 |
*, +, ?, {n}, {n,}, {n,m} | 限定符 |
^, $, \任何元字符、任何字符 | 定位点和序列(即位置与顺序) |
| | 替换,“或”操作 |
JavaScript RegExp对象
创建正则表达式
JavaScript有两种方式创建一个正则表达式:
第一种方法是直接通过/正则表达式/
写出来,第二种方式是通过new RegExp('正则表达式')
创建一个 RegExp 对象:
const re1 = /ABC\-001/;
const re2 = new RegExp('ABC\\-001');
re1; // /ABC\-001/
re2; // /ABC\-001/
test()方法
RegExp对象的test()方法用于测试给定的字符串是否符合条件。
const re = /^\d{3}\-\d{3,8}$/;
re.test('010-12345'); // true
re.test('010-1234x'); // false
re.test('010 12345'); // false
exex()方法
如果正则表达式中定义了组,就可以在RegExp对象上用exec()方法提取出子串来。
exec()方法在匹配成功后,会返回一个Array,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。
exec()方法在匹配失败时返回null:
const re = /^(\d{3})-(\d{3,8})$/;
re.exec('010-12345'); // ['010-12345', '010', '12345']
re.exec('010 12345'); // null
ES6 中正则的扩展
u 修饰符
ES6 对正则表达式添加了u
修饰符,含义为"UniCode 模式",用来正确处理大于\uFFFF
的 UniCode 字符:
/^\uD83D/u.test('\uD83D\uDC2A') // false
/^\uD83D/.test('\uD83D\uDC2A') // true
正则实例对象新增unicode
属性,表示是否设置了u
修饰符:
const r1 = /hello/;
const r2 = /hello/u;
r1.unicode // false
r2.unicode // true
一旦加上u
修饰符,就会修改下面这些正则表达式的行为:
(1) 点字符
对于码点大于0xFFFF
的 UniCode 字符,点字符不能识别,必须加上u
修饰符:
const s = '𠮷';
/^.$/.test(s) // false
/^.$/u.test(s) // true
(2) UniCode 字符表示法
ES6 新增了使用大括号表示 UniCode 字符,这种表示法在正则表达式中必须加上u
修饰符,才能识别当中的大括号,否则会被解读为量词:
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
/\u{20BB7}/u.test('𠮷') // true
(3) 量词
使用u
修饰符后,所有量词都会正确识别码点大于0xFFFF
的 UniCode 字符:
/a{2}/.test('aa') // true
/a{2}/u.test('aa') // true
/𠮷{2}/.test('𠮷𠮷') // false
/𠮷{2}/u.test('𠮷𠮷') // true
(4) 预定义模式
u
修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF
的 UniCode 字符:
/^\S$/.test('𠮷') // false
/^\S$/u.test('𠮷') // true
上面代码的\S是预定义模式,匹配所有非空白字符。只有加了u
修饰符,它才能正确匹配码点大于0xFFFF
的 Unicode 字符。
(5) i 修饰符
有些 Unicode 字符的编码不同,但是字型很相近,比如,\u004B
与\u212A
都是大写的K
。
/[a-z]/i.test('\u212A') // false
/[a-z]/iu.test('\u212A') // true
上面代码中,不加u
修饰符,就无法识别非规范的K
字符。
(6) 转义
没有u
修饰符的情况下,正则中没有定义的转义(如逗号的转义\,
)无效,而在u
模式会报错:
/\,/ // /\,/
/\,/u // 报错
y 修饰符
ES6 为正则表达式添加了y
修饰符,叫做"粘连" (sticky) 修饰符。
y
修饰符的作用与g
修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g
修饰符只要剩余位置中存在匹配就可,而y
修饰符确保匹配必须从剩余的第一个位置开始,这也就是"粘连"的含义:
const s = 'aaa_aa_a';
const r1 = /a+/g;
const r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
ES6 的正则实例对象多了sticky属性,表示是否设置了y修饰符:
const r = /hello\d/y;
r.sticky // true
RegExp.prototype.flags 属性
ES6 为正则表达式新增了flags
属性,会返回正则表达式的修饰符:
// ES5 的 source 属性
// 返回正则表达式的正文
/abc/ig.source
// "abc"
// ES6 的 flags 属性
// 返回正则表达式的修饰符
/abc/ig.flags
// 'gi'
s 修饰符:dotAll 模式
匹配任意单个字符,通常有一种变通的写法:
/foo[^]bar/.test('foo\nbar')
// true
但是这种方法不太符合直觉,ES 2018 引入了 s
修饰符,使得.
可以匹配任意单个字符:
/foo.bar/s.test('foo\nbar') // true
这被称为dotAll
模式,即点(dot)代表一切字符。
正则表达式还引入了dotAll
属性,表示该正则表达式是否处于dotAll
模式:
const re = /foo.bar/s;
// 另一种写法
// const re = new RegExp('foo.bar', 's');
re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'
后行断言
JavaScript语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。ES 2018 则引进了后行断言。
“先行断言”指的是,x
只有在y
前面才匹配,必须写成/x(?=y)/
。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/
。“先行否定断言”指的是,x
只有不在y
前面才匹配,必须写成/x(?!y)/
。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/
:
/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"]
/\d+(?!%)/.exec('that’s all 44 of them') // ["44"]
“后行断言”正好与“先行断言”相反,x
只有在y
后面才匹配,必须写成/(?<=y)x/
。比如,只匹配美元符号之后的数字,要写成/(?<=\$)\d+/
。“后行否定断言”则与“先行否定断言”相反,x
只有不在y
后面才匹配,必须写成/(?<!y)x/
。比如,只匹配不在美元符号后面的数字,要写成/(?<!\$)\d+/
:
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90') // ["90"]