正则表达式

449 阅读5分钟

正则表达式,对于前端同学的我来说用得不多,也就一直没去研究,当需要写正则时候就是一头雾水,包括配置webpack,jest等。于是在这里整理了一些JavaScritpt正则相关的内容,以此对正则表达式有一个基本的认识并能够运用正则处理日常需求。

基本概念

  • 正则表达式

    它描述了一种字符串匹配的模式,即要寻找符合这一模式(某种规则)的子串,找到后进行提取或替换。

  • 元字符

    在正则表达式中具有特殊含义的字符,如+号,代表前面的字符必须至少出现一次(1 次或多次),iphone+x,可以匹配 iphonex、iphoneex、iphoneeeeex 等。* 号代表字符可以不出现,也可以出现一次或者多次 iphone*x,可以匹配 iphonx、iphonex、iphoneeex 等。如果要匹配字符串中的特殊符号,则需要对其进行转义,即在其前加一个\,如iphone\+plus 匹配 iphone+plus。 以下是正则中的特殊字符:

    ( ) [ ] { } \ ^ $ | ? * + .
    
  • 子模式

    在正则表达式中,可以使用括号()将模式中的子字符串括起来,以形成一个子模式。将子模式视为一个整体时,那么它就相当于单个字符。

  • 贪婪与非贪婪匹配

    贪婪模式在整个表达式匹配成功的前提下尽可能多的匹配,而非贪婪模式在匹配成功的前提下尽可能少的匹配。在 js 的正则中,默认为贪婪模式,可通过在代表数量的标示符后放置 ? 来开启非贪婪模式,如 ? 、+? 甚至是 ??。

    'aaaaa'.match('a+');//贪婪模式,匹配aaaaa
    'aaaaa'.match('a+?');//非贪婪模式,匹配a
    
    

正则表达式形式

正则表达式是包含在两个斜杠之间的一个或多个字符,在后一个斜杠的后面,可以指定一个或多个选项。正则表达式也可以通过对象 RegExp 的构造函数来生成,带有变量的字符串通过 RegExp 能够比较简单地生成正则表达式,而采用/pattern/的方式变量会识别成字面量。

 var regExp = /pattern/flags
 或 var regExpStr = new RegExp(regStr, pattern)

其中,“pattern”为指定的匹配模式,flags 为可选项,这些选项及其含义如下:

  • i:表示忽略大小写,就是在字符串匹配的时候不区分大小写。

    var str = "Apple";
    var patt = /apple/;
    console.log(str.match(patt));
    => null
    var patt = /apple/i;
    console.log(str.match(patt));
    =>["Apple", index: 0, input: "Apple"]
    
  • g:表示全局匹配,即匹配符合模式的所有子串。

    var str = "meatvsmeat";
    var patt = /meat/;
    console.log(str.match(patt));
    =>["meat", index: 0, input: "meatvsmeat"]
    
    var patt = /meat/g;
    console.log(str.match(patt));
    =>["meat", "meat"]
    
  • m:表示进行多行匹配

    var str="This is a\n dog"; //\n代表换行符
    var patt=/a$/;//$代表输入的结束位置
    console.log(str.match(patt));
    => null
    
    var patt=/a$/m;
    console.log(str.match(patt));
    => ["a", index: 8, input: "This is a↵ dog"]
    

正则表达式常用方法

  • exec

    查找字符串中匹配正则的字符串,返回一个数组(未匹配到则返回 null),和 string 的 match 方法相同。

    var reg = /a/;
    reg.exec('abc');
    => ["a", index: 0, input: "abc", groups: undefined]
    
  • test

    测试字符串是否匹配正则,匹配返回 true,不匹配返回 false。

    var reg = /a/;
    reg.test('abc');
    => true
    

正则表达式的优先级

正则表达式同算术表达式类似,都是从左到右进行计算,并遵循优先级顺序。下面列出了优先级从高到低的顺序:

  • \: 转义字符
  • (), (?:), (?=), []: 圆括号与方括号
  • *, +, ?, {n}, {n,}, {n,m}: 限定符
  • ^, $, \任何元字符、任何字符: 定位点和序列
  • |: 替换,"或"操作

来看下面的例子:

/^a|bc$/

上面的正则表达式根据从左到右的顺序以及优先级关系,等同于:

/(^a)|((bc)$)/

可以看到,它所表达的意思就是以 a 开头或 bc 结尾的字符串。

/^a|bc$/.test('adfd'); //true
/^a|bc$/.test('dfdb'); //false
/^a|bc$/.test('dfdbc'); //true

JS 中字符串的匹配和替换

  • 字符串匹配

    语法:str.match(regExp)

    参数:regExp 为一个正则表达式

    返回值:如果字符串匹配到了表达式则返回一个数组,数组的第一个元素为匹配到的内容,之后的项是小括号捕获的内容,如果没有匹配到则返回 null。

    例:匹配数字

    var str = "you know that I am 17 years old";
    //\d表示数字+号表示出现多次,即可匹配数字17
    var patt = /I am (\d+)/;
    
    str.match(patt)
    //17为小括号捕获的内容,index为匹配的位置(字符I的位置)
    =>  ["I am 17", "17", index: 0, input: "I am 17 years old", groups: undefined]
    
    
  • 字符串替换

    语法:str.replace(regexp|str, newSubstr|func)

    参数:第一个参数为一个正则表达式或子串,第二个参数为要替换的内容或一个返回替换内容的函数

    例:匹配数字

    /*
      match: 匹配的结果
      s1: 第一个小括号匹配到的内容
      s2: 第二个小括号匹配到的内容
      offset: 匹配到的位置
      string: 要匹配的字符串
    */
    function replacer(match, s1, s2, offset, string) {
      //s1: 67,s2: 123,offset: 1(即a67bc123中6的位置)
      return [s1, s2].join(' - ');
    }
    var newString = 'a67bc123'.replace(/a(\d+)bc(\d+)/, replacer);
    或var newString = 'a67bc123'.replace(/a(\d+)bc(\d+)/, `$1 - $2`);
    => newString: "67 - 123"
    

特殊字符用法

  • \ 标记其后的字符为特殊字符、转义字符

    \r代表回车符,\b表示一个字符边界,反斜杠\也可以将其后的特殊字符转义为字面量,例如模式/a*/会匹配 0 个或者多个 a。相反,模式/a\*/*的特殊性移除,从而可以匹配像a*这样的字符串。\\表示一个\,第一个\为转义字符,第二个\为特殊字符\

  • ^ 匹配输入的开始位置

    如果多行标志为 true 时也匹配换行符后紧跟的位置。

    匹配以 snow 开头的字符串

    ///^snow/匹配snow is my name但不匹配my name is snow
    var str = "snow is my name";
    var patt = /^snow/;
    
  • $ 匹配输入结束的位置

    匹配输入的结束,如果多行标示为 true 时也匹配换行符前的位置。

    //如下/snow$/匹配my name is snow但不匹配snow is my name
    var str = "my name is snow";
    var patt = /snow$/;
    

    ^$结合可以匹配 snow(snow 为开始和结束即仅匹配 snow,不匹配 snow is snow)

    var str = "snow";
    var patt = /^snow$/;
    
  • | 类似或,指明两项之间的一个选择

    匹配苹果或香蕉

    var str = "banana";
    var patt = /apple|banana/;
    
  • {n} 匹配前一项连续出现 n 次

    如,/p{2}/不会匹配 aple 中的 p,但会匹配 apple 中所有的 p。

    var str = "apple is a apple";
    var patt = /(apple){2}/;
    console.log(str.match(patt)); => null
    
    var str = "appleapple 2 apple";
    var patt = /(apple){2}/;
    console.log(str.match(patt)); => appleapple
    
  • {m,n} 匹配前一项连续出现 m 到 n 次,包括 m、n 次

    var str = "apple is a apple";
    var patt = /(apple){2,3}/;
    console.log(str.match(patt)); => null
    
    var str = "appleapple 2 apple";
    var patt = /(apple){2,3}/;
    console.log(str.match(patt)); => appleapple
    
  • . 默认情况下, 句点匹配除新行符(换行符\n和回车符\r)外的任何单个字符

    匹配 snow*,如下/snow./可以匹配 snow1,snowa,snow*等。

    var str = "snow_";
    var patt = /snow./;
    
  • * 匹配零个或多个前一项,等价于 {0,}。

    如下 a*可以匹配 aabc,aaaabc,甚至不包含 "a" 的任意字符串.

    var str = "aaaabaac";
    var patt = /ba*/;
    console.log(str.match(patt)) => baa
    

    句点星号模式 .* 可以匹配零个或多个任意字符 (除了新行符: \r\n)。例如, snow.*123 可以匹配 snowAnything123, 也能匹配 snow123.

  • + 匹配前一项一个或多个,等价于 {1,}。

    例如,/p+/匹配了"apppppple" 中的 'p'。

  • ? 匹配零个或一个前面的字符,可以理解为"前面的那项是可选的"。

    例如, colou?r 可以匹配 color 和 colour, 因为 "u" 是可选的, abcd(efg)?hij 可以匹配 abcdhij 和 abcdefghij。 此外,将?放到表示数量的标识符后边表示非贪婪匹配。

  • () 标记一个子表达式(分组)的开始和结束位置

    每一个子表达式会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为 1,第二个为 2,以此类推。子表达式的结果可以获取供以后使用。

    
    var str = "abc";
    str.match(/((a)(b))(c)/);
    ["abc",
    "ab", //第一个外层括号捕获的内容
    "a", //第一个外层括号内第一个括号捕获的内容
    "b",//第一个外层括号内第二个括号捕获的内容
    "c",//与第一个外层括号平级的括号捕获的内容
    index: 0, input: "abc", groups: undefined];
    
    var str = "apple is a iphone";
    var patt = /i(phone\$)/;
    console.log(str.match(patt));
    => [ 'iphone', 'phone', index: 11, input: 'apple is a iphone' ]
    
    var str = "there is a iphonePlus called iphonePlus";
    var patt = /(iphone)(Plus).\*\1\2\$/; //\1、\2 表示子表达式 1、2 的结果,即 iphone、Plus
    console.log(str.match(patt));
    => [ 'iphonePlus called iphonePlus',
    'iphone',
    'Plus',
    index: 11,
    input: 'there is a iphonePlus called iphonePlus' ]
    
    
  • (x) 匹配 x 并且记住匹配项,括号被称为捕获括号。

    var str = "there is a iphone called iphone";
    var patt = /(iphone).*\1$/; //因为记住了匹配项,所有\1 能够拿到子表达式 1 的结果 iphone
    console.log(str.match(patt));
    =>[ 'iphone called iphone',
    'iphone',
    index: 11,
    input: 'there is a iphone called iphone' ]
    
    
  • (?:x) 匹配 x 但是不记住匹配项。这种叫作非捕获括号

    var str = "there is a iphone called iphone";
    var patt = /(?:iphone).*\1\$/; //因为没有记住匹配项,所有\1 不能拿到子表达式 1 的结果,匹配为 null
    console.log(str.match(patt));
    =>null
    
    
  • x(?=y) 匹配 x 仅仅当 x 后面跟着 y,这叫做正向肯定查找。

    var str = "iphoneX and iphonePlus";
    var patt = /iphone(?=Plus)/; //匹配 iphonePlus 的 iphone
    console.log(str.match(patt));
    =>[ 'iphone', index: 12, input: 'iphoneX and iphonePlus' ]
    
    
  • x(?!y) 匹配 x 仅仅当 x 后面不跟着 y,这个叫做正向否定查找。

    var str = "iphoneX and iphonePlus";
    var patt = /iphone(?!Plus)/;//匹配 iphoneX 的 iphone
    console.log(str.match(patt));
    => [ 'iphone', index: 0, input: 'iphoneX and iphonePlus' ]
    
    
  • [xyz] 字符类:元字符[]指定正则表达式中的字符类。 字符类[123]将匹配字符 1,2 或 3。例如,正则表达式“m[ae]n"可以匹配字符串“man"或“men"。 可以使用连字符-来指定字符范围,如[A-Z]表示所有大写任何字母;[0-9]表示 0 和 9 之间的任何数字。[a-ex-z]将包括 a,b,c,d, x,y 或 z。

  • [^xyz] 一个反向字符集。^表示除什么以外,也就是说, 它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。任何普通字符在这里都是起作用的。 [^abc][^a-c] 表示除 a,b 和 c 以外的字符。

  • [\b] 匹配一个退格(U+0008)。(不要和\b 混淆了)

  • \b 匹配一个单词的边界,一个词的边界就是一个词不被另外一个字符跟随的位置或者没有其他字符在其前面的位置。 /\bm/匹配“moon”中得‘m’; /oo\b/并不匹配"moon"中得'oo',因为'oo'被一个字符'n'紧跟着。 /oon\b/匹配"moon"中得'oon',因为'oon'是这个字符串的结束部分,他没有被一个字符紧跟着。

  • \B 匹配一个非单词边界 例如,/\Bday/能够匹配"noonday"中的'day', 而/y\B/匹配"possibly yesterday"中 yesterday 的首字母’y‘。

  • \d 匹配一个数字。 等价于[0-9]。 例如, /\d/ 或者 /[0-9]/ 匹配"B2 is the suite number."中的 2。

  • \D 匹配一个非数字字符。 等价于[^0-9]。 例如, /\D/ 或者 /[^0-9]/ 匹配"B2 is the suite number."中的 B。

  • \f 匹配一个换页符 (U+000C)。

  • \n 匹配一个换行符 (U+000A)或捕获的子串。 在正则表达式中,它返回最后的第 n 个子捕获匹配的子字符串(捕获的数目以左括号计数)。 比如 /apple(,)\sorange\1/ 匹配"apple, orange, cherry, peach."中的'apple, orange,' 。

  • \r 匹配一个回车符 (U+000D)。

  • \s 匹配一个空白字符,包括空格、制表符、换页符和换行符。 等价于[ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。 例如, /\s\w*/ 匹配"foo bar."中的' bar'。

  • \S 匹配一个非空白字符。 等价于[^ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。 例如, /\S\w*/ 匹配"foo bar."中的'foo'。

  • \t 匹配一个水平制表符 (U+0009)。

  • \v 匹配一个垂直制表符 (U+000B)。

  • \w 匹配一个单字字符(字母、数字或者下划线)。 等价于[A-Za-z0-9_]。 例如, /\w/ 匹配 "apple," 中的 'a',"$5.28,"中的 '5' 和 "3D." 中的 '3'。

  • \W 匹配一个非单字字符。 等价于[^a-za-z0-9_]。 例如, /\W/ 或者 /[^a-za-z0-9_]/ 匹配 "50%." 中的 '%'。

  • \0 匹配 NULL (U+0000) 字符, 不要在这后面跟其它小数,因为 \0 是一个八进制转义序列。

  • \xhh 与代码 hh 匹配字符(两个十六进制数字)

  • \uhhhh 与代码 hhhh 匹配字符(四个十六进制数字)。

  • ?#comment注释

    var patt = /\/(?#匹配斜杠/)/;
    

实例

  • 匹配任意字符

    ([\s\S]*)
    
  • 匹配最后一个斜杠后的,最后一个点前的内容

    //要匹配目标内容,可以从后往回推,首先是后缀名(.*表示),后缀名前跟着一个'.'(\.表示),'.'前则是目标内容(.*),用括号捕获,目标内容前为斜杠(\/表示),斜杠前为任意内容,正则表达式如下:
    var r =/.*\/(.*)\..*/;
    var m = s.match(r);
    console.log(str.match(patt));
    => ["http://i2.xxx.com/wx/images_2016/monkey/m4_1e3thtr.png","m4_1e3thtr"]
    

    这里的.*\/可以匹配最后一个斜杠前的所有内容,因为 js 中的正则默认是贪婪模式。

  • 验证字符串组成,第一个为数字,后面可以是字母、数字、下划线组成的 1 到 50 位的字符。

    str.match(/\d\w{1,50}$/)

  • 将一浮点数小数点左边的数字每三位加一个逗号

    
    //当一个字符串中某个数字后跟着 n 对三个数字(\d{3})就匹配这个数字
    const formateNum = (num) => {
      const numStr = num.toString();
      //替换小数点左边的数字
      return numStr.replace(/\d+/, (match) => {
          //替换目标数字为`${matchNum},`
          return match.replace(/(\d)(?=((\d{3})+$))/g, (s1) => `${s1},`);
        })
    }
    或
    const formateNum = (num) => {
      const numStr = num.toString();
       //替换非单词边界,当这个边界后面跟着n对三个数字且后边没有数字时
      return numStr.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    }
    
    formateNum(435345.656)
    => "435,345.656"
    
    
  • 匹配一个字符串当这个字符串中没有@这个字符

    如下: 正则/^(?!.*@)/表示如果这个字符串中没有@字符就匹配字符串开始的位置,如果不添加开始符号^会因为@后面的字符导致匹配结果不对(比如abcd@efg并不满足正则(?!.*@),从而匹配成功)。

    var reg = /^(?!.\*@)/;
    reg.test('abcd@efg');
    => false
    reg.test('abcdefg');
    => true