先简单看几个常用基础标识符
^ 匹配一个输入或一行的开头,
/^a/
// 匹配"an A",而不匹配"An a"
$ 匹配一个输入或一行的结尾
/a$/
// 匹配"An a",而不匹配"an A"
*匹配前面元字符0次或多次
/ba*/
// 匹配b,ba,baa,baaa,...
+匹配前面元字符1次或多次
/ba+/
// 匹配ba,baa,baaa,...
? 匹配前面元字符0次或1次
/ba?/
// 匹配b,ba
(x) //匹配x保存x在名为$1...$9的变量中
x|y //匹配x或y
{n} //精确匹配n次
{n,} //匹配n次以上
{n,m} //匹配n-m次
[xyz] //字符集,匹配这个集合中的任一一个字符(或元字符),匹配x,y,z
[^xyz] //不匹配这个集合中的任何一个字符
正则表达式(Regular Expression)其实是一门工具,通过字符串模式匹配,实现搜索和替换功能。
它起源于20世纪50年代科学家在数学领域做的一些研究工作,后来才被引入到计算机领域中。
从它的命名我们可以知道,它是一种用来描述规则的表达式。而它的底层原理也十分简单,就是使用状态机的思想进行模式匹配。
这里先不细纠概念,常用的方法属性,文档mdn什么都有,也不纠结,直奔主题。
子表达式
子表达式(Reg)具有独立的匹配功能,保存独立的匹配结果
作为独立单元可以使用*+?{n,m}等量词
/(ab)?c)/ // 匹配c或者abc
作为子模式可以独立处理,并且保留匹配结果子串,可通过RegExp.$1,...$n访问
var re = /(\w+)\s(\w+)/; var str = "John Smith"; var newstr = str.replace(re, "$2, $1"); console.log(newstr); // Smith, John
以下RegExp属性,不是w3c标准,不过大部浏览器支持
RegExp属性 描述 $n 第 n 个子表达式匹配的字符串,只有1-9 $& 最后匹配到的字符串,RegExp.lastMatch别名 $' 最新匹配的右侧子串,RegExp.rightContext 别名 $` 最新匹配的左侧子串,RegExp.leftContext别名 $+ 匹配到的最后一个子串,RegExp.lastParen别名 $_ 被匹配成功的原字符串,RegExp.input别名 对应replace函数中访问正则结果的参数
变量名 描述 $n 插入第 n 个子表达式匹配的字符串,只有1-99 $& 插入匹配的子串 $' 插入当前匹配的子串右边的内容 $` 插入当前匹配的子串左边的内容 $ 匹配[Name]具名子表达式的字符串 回溯引用(反向引用),模式的后面部分引用前面子表达式已经匹配到的子字符串
通过反斜杠\加数字来实现的。数字代表子表达式在该正则表达式中的顺序。例如: \1 引用的是第一个子表达式的匹配结果是匹配结果不是匹配模式
var s = "
title
text
"; var r = /(<\/?\w+>).*\1/g; // 相当于/(<\/?\w+>).*(
|
)/g // 再加(<\/?\w+>)匹配结果 === (
|
)匹配结果 var a = s.match(r); //返回数组["
title
","
text
"]
子表达式的高级模式
非捕获模式,匹配结果不会保留
/(?:\w+)\s(\w+)/
// ?:标识非捕获
// $1 从第二个子表达式开始计算
命名捕获,这个用的较少
var re = /(?\w+)\s(\w+)/;
console.log("John Smith".match(re));
// (?x)
// 匹配结果保存在匹配项的 groups 属性中
断言
断言用来限制正则匹配的边界。
其实^,&,\b,\B也是断言。
- ^ 对应字符串开头
- & 字符串结尾
- \b 单词边界
- \B 非单词边界
这里主要说其他四种断言
- 先行断言,/x(?=y)/ y在后面跟随x的时候匹配x
- 先行否定断言,/x(?!y)/ y没有在后面跟随x的时候匹配x
- 后行断言,/(?<=y)x/ y在x前面紧随的时候匹配x
后行否定断言,/(?
// 先行断言 let regex = /First(?= test)/g; console.log('First test'.match(regex)); // [ 'First' ] // 先行否定断言 // /\d+(?!\.)/ 匹配没有被小数点跟随且至少有一位的数字。 /\d+(?!\.)/.exec('3.141') 匹配 "141" 而不是 "3" console.log(/\d+(?!\.)/g.exec('3.141')); // [ '141', index: 2, input: '3.141' ] // abc后面不能跟随de let reg = /abc(?!de)/; reg.test('abcdefg'); // false; reg.test('abcd'); // true; reg.test('abcabc'); // true;
正则表达式的三种模式
在使用修饰匹配次数的特殊符号时,有几种表示方法可以使同一个表达式能够匹配不同的次数,比如:"{m,n}", "{m,}", "?", "*", "+",具体匹配的次数随被匹配的字符串而定。
贪婪模式
贪婪模式总是尽可能多的匹配var regex = /\d{2,5}/g; var string = "123 1234 12345 123456"; console.log( string.match(regex) ); // => ["123", "1234", "12345", "12345"]
懒惰模式
在修饰匹配次数的特殊符号后再加上一个 "?" 号,则可以使匹配次数不定的表达式尽可能少的匹配,使可匹配可不匹配的表达式,尽可能的 "不匹配"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"] 其中 /\d{2,5}?/ 表示,虽然 2 到 5 次都行,当 2 个就够的时候,就不再往下匹配
- 独占模式(js不支持)
如果在表达式后加上一个加号(+),则会开启独占模式。同贪婪模式一样,独占模式一样会匹配最长。不过在独占模式下,正则表达式尽可能长地去匹配字符串,一旦匹配不成功就会结束匹配而不会回溯。
正则的性能
正则引擎主要的两大类:一种是DFA(确定型有穷自动机),另一种是NFA(不确定型有穷自动机)。NFA 对应正则表达式主导的匹配,DFA 对应文本主导的匹配。
DFA从匹配文本入手,从左到右,每个字符不会匹配两次,它的时间复杂度是多项式的,所以通常情况下,它的速度更快,但支持的特性很少,不支持捕获组、各种引用等等;
NFA则是从正则表达式入手,不断读入字符,尝试是否匹配当前正则,不匹配则吐出字符重新尝试,通常它的速度比较慢,最优时间复杂度为多项式,最差情况为指数级。
NFA支持更多的特性,因而绝大多数编程场景下(包括java,js),我们面对的是NFA。
吐出字符重新尝试就是回溯。