1

先简单看几个常用基础标识符

^ 匹配一个输入或一行的开头,

  /^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)具有独立的匹配功能,保存独立的匹配结果

  1. 作为独立单元可以使用*+?{n,m}等量词

    /(ab)?c)/ 
    // 匹配c或者abc
  2. 作为子模式可以独立处理,并且保留匹配结果子串,可通过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>匹配[Name]具名子表达式的字符串
  3. 回溯引用(反向引用),模式的后面部分引用前面子表达式已经匹配到的子字符串
    通过反斜杠\加数字来实现的。数字代表子表达式在该正则表达式中的顺序。例如: \1 引用的是第一个子表达式的匹配结果

    匹配结果不是匹配模式
    var s = "<h1>title<h1><p>text<p>";
    var r = /(<\/?\w+>).*\1/g;
    // 相当于/(<\/?\w+>).*(<h1>|<p>)/g
    // 再加(<\/?\w+>)匹配结果 === (<h1>|<p>)匹配结果
    var a = s.match(r);  //返回数组["<h1>title<h1>","<p>text<p>"]

子表达式的高级模式

非捕获模式,匹配结果不会保留
/(?:\w+)\s(\w+)/
// ?:标识非捕获
// $1 从第二个子表达式开始计算
命名捕获,这个用的较少
var re = /(?<myName>\w+)\s(\w+)/;
console.log("John Smith".match(re));
// (?<Name>x)
// 匹配结果保存在匹配项的 groups 属性中

1672889918820.png

断言

断言用来限制正则匹配的边界。

其实^,&,\b,\B也是断言。

  • ^ 对应字符串开头
  • & 字符串结尾
  • \b 单词边界
  • \B 非单词边界

这里主要说其他四种断言

  1. 先行断言,/x(?=y)/ y在后面跟随x的时候匹配x
  2. 先行否定断言,/x(?!y)/ y没有在后面跟随x的时候匹配x
  3. 后行断言,/(?<=y)x/ y在x前面紧随的时候匹配x
  4. 后行否定断言,/(?<!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;

注意:匹配结果是不包括y的

正则表达式的三种模式

在使用修饰匹配次数的特殊符号时,有几种表示方法可以使同一个表达式能够匹配不同的次数,比如:"{m,n}", "{m,}", "?", "*", "+",具体匹配的次数随被匹配的字符串而定。

  1. 贪婪模式
    贪婪模式总是尽可能多的匹配

    var regex = /\d{2,5}/g;
    var string = "123 1234 12345 123456";
    console.log( string.match(regex) );
    // => ["123", "1234", "12345", "12345"]
  2. 懒惰模式
    在修饰匹配次数的特殊符号后再加上一个 "?" 号,则可以使匹配次数不定的表达式尽可能少的匹配,使可匹配可不匹配的表达式,尽可能的 "不匹配"

    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 个就够的时候,就不再往下匹配
  3. 独占模式(js不支持)
    如果在表达式后加上一个加号(+),则会开启独占模式。同贪婪模式一样,独占模式一样会匹配最长。不过在独占模式下,正则表达式尽可能长地去匹配字符串,一旦匹配不成功就会结束匹配而不会回溯。

正则的性能

正则引擎主要的两大类:一种是DFA(确定型有穷自动机),另一种是NFA(不确定型有穷自动机)。NFA 对应正则表达式主导的匹配,DFA 对应文本主导的匹配。

DFA从匹配文本入手,从左到右,每个字符不会匹配两次,它的时间复杂度是多项式的,所以通常情况下,它的速度更快,但支持的特性很少,不支持捕获组、各种引用等等;

NFA则是从正则表达式入手,不断读入字符,尝试是否匹配当前正则,不匹配则吐出字符重新尝试,通常它的速度比较慢,最优时间复杂度为多项式,最差情况为指数级

NFA支持更多的特性,因而绝大多数编程场景下(包括java,js),我们面对的是NFA。

吐出字符重新尝试就是回溯

正则表达式回溯法原理


肥皂泡
382 声望6 粉丝

码农