JavaScript——够正的正则表达式!!!

正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。

  • 在 JavaScript中,正则表达式也是对象。
  • 正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
  • 一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单字符和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。
  • 正则表达式里面不需要加引号 不管是数字型还是字符串型
  • 正则表达式学习网址: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
  • 正则表达式测试网址:http://tool.oschina.net/regex

1. 1 使用场景

  1. RegExpexectest方法

    • exec

      • 语法:

        // reg:RegExp实例
        // str:要匹配正则表达式的字符串
        reg.exec(str)
        
      • 返回值:

        如果匹配成功,exec() 方法返回一个数组,包含额外的属性indexinput,并更新正则表达式对象的 lastIndex属性。完全匹配成功的文本将作为返回数组的第一项,从第二项起,后续每项都对应正则表达式内捕获括号里匹配成功的文本。

        如果匹配失败,exec()方法返回null,并将lastIndex重置为0

      • 例子

        var re = /quick\s(brown).+?(jumps)/ig;
        var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog.');
        

        结果如下表:

        对象属性/索引描述例子
        result[0]匹配的全部字符串Quick Brown Fox Jumps
        [1],...[n]括号中的分组捕获result[1] = Brown result[2] = Jumps
        index匹配到的字符位于原始字符串的基于0的索引值4
        input原始字符串The Quick Brown Fox Jumps Over The Lazy Dog
        relastIndex下一次匹配开始的位置25
        ignoreCase是否使用了 "i" 标记使正则匹配忽略大小写true
        global是否使用了 "g" 标记来进行全局的匹配true
        multiline是否使用了 "m" 标记使正则工作在多行模式(也就是,^ 和 $ 可以匹配字符串中每一行的开始和结束(行是由 \n 或 \r 分割的),而不只是整个输入字符串的最开始和最末尾处。)false
        source正则匹配的字符串quick\s(brown).+?(jumps)
    • test

      • 语法:

        // reg:RegExp实例
        // str:要匹配正则表达式的字符串
        reg.test(str)
        
      • 返回值:

        test()用于检测字符串是否符合该规则,匹配成功返回true,否则返回false

      • 例子:

        var rg = /abc/;
        console.log(rg.test('abc')); //true
        console.log(rg.test('aabcd')); //true
        console.log(rg.test('abcd')); //true
        console.log(rg.test('abbc')); //false
        
  2. Stringmatch matchAll replace search split方法

    • match

      • 语法:

        str.match(reg)
        
      • 返回值:

        • 如果使用g标志,则将返回与完整正则表达式匹配的所有结果,不会返回捕获组。

        • 如果未使用g标志,则仅返回第一个完整匹配及其相关的捕获组(Array)。 在这种情况下,返回的项目将具有如下所述的其他属性:

          • groups: 一个捕获组数组 或undefined(如果没有定义命名捕获组)。
          • index: 匹配的结果的开始位置
          • input: 搜索的字符串.

          事实上,如果正则表达式不包含g,str.match(reg)将返回与reg.exec(str)相同的结果.

      • 例子:

        var str = 'For more information, see Chapter 3.4.5.1';
        var re = /see (chapter \d+(\.\d)*)/i;
        var found = str.match(re);
        
        console.log(found);
        
        // logs [ 'see Chapter 3.4.5.1',
        //        'Chapter 3.4.5.1',
        //        '.1',
        //        index: 22,
        //        input: 'For more information, see Chapter 3.4.5.1' ]
        
        // 'see Chapter 3.4.5.1' 是整个匹配。
        // 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕获。
        // '.1' 是被'(\.\d)'捕获的最后一个值。
        // 'index' 属性(22) 是整个匹配从零开始的索引。
        // 'input' 属性是被解析的原始字符串。
        
    • matchAll

      • 语法:

        // reg:正则表达式对象,且必须是设置了全局`g`的形式,否则会抛出异常`TypeError`
        str.matchAll(reg)
        
      • 返回值:

        返回一个迭代器,可用forArray.from来遍历

      • 说明:

        1. 对比reg.exec()str.matchAll():

          matchAll 出现之前,通过在循环中调用 reg.exec() 来获取所有匹配项信息(reg 需使用 /g 标志):

          const reg = RegExp('foo[a-z]*','g');
          const str = 'table football, foosball';
          let match;
          while ((match = reg.exec(str)) !== null) {
              console.log(`Find: ${match[0]} start: ${match.index} end: ${reg.lastIndex}`);
              // Find: football start: 6 end: 14
              // test.html:93 Find: foosball start: 16 end: 24
          }
          

          如果使用 matchAll ,就可以不必使用 while 循环加 exec 方式(且正则表达式需使用 /g 标志)。使用 matchAll 会得到一个迭代器的返回值,配合 for...of, array spread, 或者 Array.from() 可以更方便实现功能:

          const reg = RegExp('foo[a-z]*','g');
          const str = 'table football, foosball';
          const match = str.matchAll(reg);
          for (const item of match) {
          	console.log(`Find ${item[0]} start: ${item.index} end: ${item.index + item[0].length}`);
          	// Find: football start: 6 end: 14
              // test.html:93 Find: foosball start: 16 end: 24
          }
          const arr = Array.from(str.matchAll(reg), m => m[0]);
          console.log(arr); // ["football", "foosball"]
          
        2. matchAll内部做了一个reg的复制,所以不像reg.exec,lastIndex在字符串扫描时不会改变

          const regexp = RegExp('[a-c]','g');
          regexp.lastIndex = 1;
          const str = 'abc';
          Array.from(str.matchAll(regexp), m => `${regexp.lastIndex} ${m[0]}`);
          // Array [ "1 b", "1 c" ]
          
        3. matchAll 的另外一个亮点是更好地获取捕获组。因为当使用 match()/g 标志方式获取匹配信息时,捕获组会被忽略:

          var regexp = /t(e)(st(\d?))/g;
          var str = 'test1test2';
          
          str.match(regexp);
          // Array ['test1', 'test2']
          

          使用 matchAll 可以通过如下方式获取分组捕获:

          let array = [...str.matchAll(regexp)];
          
          array[0];
          // ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
          array[1];
          // ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
          
    • replace

      • 语法:

        // reg:一个RegExp对象或者其字面量.该正则所匹配的内容会被第二个参数的返回值替换
        // substr:一个将被newSubstr替换的字符串.仅第一个匹配项会被替换.
        // newSubstr:用于替换掉第一个参数在原字符串中的匹配部分的字符串。该字符串中可以内插一些特殊的变量名。
        // replaceFunction:一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果
        str.replace(reg|substr, newSubstr|replaceFunction)
        
      • 返回值:

        该方法并不改变调用它的字符串本身,而只是返回一个新的替换后的字符串。

      • 第二个参数说明:

        • newSubstr可插入下面的特殊变量名:

          变量名代表的值
          $$插入一个"$"
          $&插入匹配的子串
          $`插入当前匹配的子串左边的内容
          $'插入当前匹配的子串右边的内容
          $n当第一个参数是RegExp对象且包含捕获组,$n对应第n个捕获组匹配的子串
          $<name>这里*Name* 是一个分组名称。如果在正则表达式中并不存在分组(或者没有匹配),这个变量将被处理为空字符串。
        • replaceFunction

          第二个参数可以指定为一个函数,函数的返回值作为替换的字符串.注意,每执行一次匹配,该函数就会执行一次(所以当第一个参数是正则表达式,并且设置了全局匹配g,那么replaceFunction会被多次调用,每次匹配都用被调用),如下,第一个aa被替换为0,第二个aa被替换为1

          const str = 'aabbaaccdd';
          const reg = /aa/g;
          let i = 0;
          function fn (match) {
          	return i++;
          }
          console.log(str.replace(reg, fn)); // 0bb1ccdd
          

          以下是该函数的参数:

          变量名代表的值
          match匹配的子串(对应于上述的$&)
          p1,p2,...对应上述的&1,&2,...
          offset匹配到的子字符串在原字符串中的偏移量.(比如,如果原字符串是abcd,匹配到的子字符串是bc,那么偏移量就是1)
          string被匹配的原字符串
          NamedCaptureGroup命名捕获组匹配的对象
      • 例子:

        1. 下面的例子将会使 newString 变成 'abc - 12345 - #$*%'

          function replacer(match, p1, p2, p3, offset, string) {
            // p1 is nondigits, p2 digits, and p3 non-alphanumerics
            return [p1, p2, p3].join(' - ');
          }
          var newString = 'abc12345#$*%'.replace(/([^\d]*)(\d*)([^\w]*)/, replacer);
          console.log(newString);  // abc - 12345 - #$*%
          
        2. 交换字符串中的两个单词:

          var re = /(\w+)\s(\w+)/;
          var str = "John Smith";
          var newstr = str.replace(re, "$2, $1");
          // Smith, John
          console.log(newstr);
          
        3. 使用行内函数来修改匹配到的字符:

          function upperToHyphenLower(match){
          	return '-' + match.toLowerCase();
          }
          console.log('borderTop'.replace(/[A-Z]/g, upperToHyphenLower)); // border-top
          

          因为我们想在最终的替换中进一步转变匹配结果(比如这里使用toLowerCase方法进行转换),所以我们必须使用一个函数.如果我们不使用一个函数进行匹配,那么toLowerCase()方法不会起效.如:

          var newString = propertyName.replace(/[A-Z]/g, '-' + '$&'.toLowerCase());  // won't work
          

          这是因为 '$&'.toLowerCase() 会先被解析成字符串字面量(这会导致相同的’$&’)而不是当作一个模式

    • search

      • 语法:

        str.search(reg)
        
      • 返回值:

        如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引;否则,返回 -1

        事实上,search相对于match,就像test相对于exec.当仅仅想知道字符串中是否存在某个pattern时,可使用searchtest,而当想获取更多的匹配信息时,可使用matchexec(会更慢一些).

      • 例子:

        var str = "hey JudE";
        var re = /[A-Z]/g;
        var re2 = /[.]/g;
        console.log(str.search(re)); // 4
        console.log(str.search(re2)); // -1
        
    • split

      • 语法:

        // separator:分隔符
        // limit:限制返回数组的项数最大值
        str.split([separator[, limit]]);
        
      • 返回值:

        1. separator为字符串,返回strseparator作为分隔符切割的子字符串的数组;
        2. separator为正则表达式,返回str匹配separator作为分隔符切割的子字符串的数组;如果分隔符是包含捕获括号的正则表达式,则每次分隔符匹配时,捕获括号的结果(包括任何未定义的结果)将被拼接到输出数组中。
        3. 如果str没有找到separator或者省略了separator.则返回包含str的数组;
      • 例子:

        const str = 'aabbccbbddee';
        console.log(str.split()); // ['aabbccbbddee']
        console.log(str.split('')); // ["a", "a", "b", "b", "c", "c", "b", "b", "d", "d", "e", "e"]
        console.log(str.split('', 4)); // ["a", "a", "b", "b"]
        console.log(str.split(/b+/)); // ["aa", "cc", "ddee"]
        console.log(str.split(/(b+)/)); // ["aa", "bb", "cc", "bb", "ddee"]
        console.log('ca,bc,a,bca,bca,bc'.split(['a','b'])); // ["c", "c,", "c", "c", "c"]
        

1.2 创建正则表达式

  1. 通过 RegExp 对象的构造函数创建:

    在脚本运行过程中,用构造函数创建的正则表达式会被编译。如果正则表达式将会改变,或者它将会从用户输入等来源中动态地产生,就需要使用构造函数来创建正则表达式。

    var 变量名 = new RegExp(/表达式/);
    
  2. 通过字面量创建

    脚本加载后,正则表达式字面量就会被编译。当正则表达式保持不变时,使用此方法可获得更好的性能。

    var 变量名 = /表达式/;
    

1.3 特殊字符

量词符
字符用法
*匹配前一个表达式 0 次或多次.等价于 {0,}
例如,/bo*/ 会匹配"A ghost boooooed"中的 'booooo'"A bird warbled" 中的 'b',但是在 "A goat grunted" 中不会匹配任何内容
+匹配前面一个表达式 1 次或者多次.等价于 {1,}
例如,/a+/ 会匹配 "candy" 中的'a'"caaaaaaandy" 中所有的'a',但是在 "cndy"中不会匹配任何内容
?1. 匹配前面一个表达式 0 次或者 1 次.等价于 {0,1}
例如,/e?le?/ 匹配 "angel" 中的 'el'"angle"中的'le' 以及"also'中的 'l';
2. 如果紧跟在任何量词* + ?或者{}的后面,会使量词变得非贪婪(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)相反.
如,对"123abc"使用/\d+/会匹配"123",而使用/\d+?/则只会匹配到""`
3. 还用于先行断言
{n}n 是一个正整数,匹配了前面一个字符刚好出现了 n 次。
比如, /a{2}/ 不会匹配“candy”中的’a’,但是会匹配“caandy”中所有的 a,以及“caaandy”中的前两个’a’。
{n,}n是一个正整数,匹配前一个字符至少出现了n次。
例如, /a{2,}/ 匹配 “aa”, “aaaa” 和 “aaaaa” 但是不匹配 “a”。
{n,m}n 和 m 都是整数。匹配前面的字符至少n次,最多m次。如果 n 或者 m 的值是0, 这个值被忽略。
例如,/a{1, 3}/ 并不匹配“cndy”中的任意字符,匹配“candy”中的a,匹配“caandy”中的前两个a,也匹配“caaaaaaandy”中的前三个a。注意,当匹配”caaaaaaandy“时,匹配的值是“aaa”,即使原始的字符串中有更多的a。
边界符
字符用法
^1. 匹配输入的开始,如果多行标志被设置为true,那么也匹配换行符后紧跟的位置.
/^A/ 并不会匹配"an A"中的'A',但是会匹配 "An E"中的'A'
2. 反向字符集合
$匹配输入的结束.如果多行标志被设置为 true,那么也匹配换行符前的位置。
例如,/t$/ 并不会匹配 "eater"中的't',但是会匹配 "eat" 中的't'

如果 ^ 和 $ 在一起,表示必须是精确匹配

var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范
console.log(reg1.test('abc')); // true
console.log(reg1.test('abcd')); // false
console.log(reg1.test('aabcd')); // false
console.log(reg1.test('abcabc')); // false
括号
  1. 小括号-捕获括号:

    字符用法
    (x)像下面的例子展示的那样,它会匹配 x并且记住匹配项。其中括号被称为捕获括号
    模式 /(foo) (bar) \1 \2/ 中的 ‘(foo)’ 和 ‘(bar)’ 匹配并记住字符串 “foo bar foo bar” 中前两个单词。模式中的 \1\2 表示第一个和第二个被捕获括号匹配的子字符串,即 foobar,匹配了原字符串中的后两个单词。注意 \1\2、…、\n 是用在正则表达式的匹配环节,详情可以参阅后文的 \n 条目。而在正则表达式的替换环节,则要使用像 $1$2、…、$n 这样的语法,例如,'bar foo'.replace(/(...) (...)/, '$2 $1')$& 表示整个用于匹配的原字符串。
    (?:x)匹配 ‘x’ 但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。
    /(?:foo){1,2}/。如果表达式是 /foo{1,2}/{1,2} 将只应用于 ‘foo’ 的最后一个字符 ‘o’。如果使用非捕获括号,则 {1,2} 会应用于整个 ‘foo’ 单词
  2. 中括号:

    字符用法
    [xyz]一个字符集合。匹配方括号中的任意字符,包括转义序列。你可以使用破折号(-)来指定一个字符范围。对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。
    例如,[abcd] 和[a-d]是一样的。他们都匹配"brisket"中的‘b’,也都匹配“city”中的‘c’。/[a-z.]+/ 和/[\w.]+/与字符串“test.i.ng”匹配。
    [^xyz]一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。任何普通字符在这里都是起作用的。
    例如,[^abc][^a-c]是一样的。他们匹配"brisket"中的‘r’,也匹配“chop”中的‘h’。
  3. 大括号:量词符

转义符
字符用法
\1. 在非特殊字符前,表示下一个字符是特殊字符,如\s匹配空白字符,而不是匹配小写字母s;
2.在特殊字符前,表示下一个字符是普通字符,即转义,如\*匹配*字符;
3.在字符串字面量中,\是转义字符,所以如果要在这种模式下添加反斜杠,需要双重转义.
/[a-z]\s/inew RegExp("[a-z]\\s", "i") 创建了相同的正则表达式
再如/[a-z]:\\/inew RegExp("[a-z]:\\\\","i") 会创建相同的表达式

关于转义:

  1. 转义特殊字符为对应的字面值,如匹配a*b,使用/a\*b/;

  2. 转义斜杆/,如果正则表达式文字需要匹配斜杠/,需要进行转义,因为/在正则中是终止符.如匹配C:/,使用/[A-Z]:\//;

  3. 转义反斜杠\,如果正则表达式文字需要匹配反斜杠\,需要进行转义.如匹配C:\,使用/[A-Z]:\\/;

  4. RegExp构造函数中,在字符串中使用正则表达式,请记住反斜杠是字符串文字中的转义,因此要在正则表达式中使用它,需要在字符串文字级别转义它。 /a\*b/new RegExp("a\\*b")创建的表达式是相同的

  5. 将用户输入转义为正则表达式中的一个字面字符串:

    function escapeRegExp(inputString) {
        //$&表示整个被匹配的字符串
        return inputString.replace(/[.*+?^&{}()|[\]\\]/g, "\\$&");
    }
    
断言
字符用法
x(?=y)匹配’x’仅仅当’x’后面跟着’y’.这种叫做先行断言。
例如,/Jack(?=Sprat)/会匹配到’Jack’仅当它后面跟着’Sprat’。/Jack(?=Sprat|Frost)/匹配‘Jack’仅当它后面跟着’Sprat’或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。
(?<=y)x匹配’x’仅当’x’前面是’y’.这种叫做后行断言。
例如,/(?<=Jack)Sprat/会匹配到’ Sprat ‘仅仅当它前面是’ Jack '。/(?<=Jack|Tom)Sprat/匹配‘ Sprat ’仅仅当它前面是’Jack’或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。
x(?!y)仅仅当’x’后面不跟着’y’时匹配’x’,这被称为正向否定查找。
例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。正则表达式/\d+(?!.)/.exec(“3.141”)匹配‘141’而不是‘3.141’
(?<!y)x仅仅当’x’前面不是’y’时匹配’x’,这被称为反向否定查找。
例如, 仅仅当这个数字前面没有负号的时候,/(?<!-)\d+/ 匹配一个数字。
/(?<!-)\d+/.exec('3') 匹配到 “3”.
/(?<!-)\d+/.exec('-3') 因为这个数字前有负号,所以没有匹配到。
预定义类
字符用法
\b匹配一个词的边界.
/\blu/匹配lujiafeng中的lu
/lu\b/匹配lujiafeng中的lu
\B
\d匹配一个数字。``等价于[0-9]
\D匹配一个非数字字符。``等价于[^0-9]
\f匹配一个换页符 (U+000C)
\n匹配一个换行符 (U+000A)。
\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%.” 中的 ‘%’。
\n(这里的n指的是数字)在正则表达式中,它返回最后的第n个子捕获匹配的子字符串(捕获的数目以左括号计数)。
比如 /apple(,)\sorange\1/ 匹配"apple, orange, cherry, peach."中的’apple, orange,’ 。
\0匹配 NULL(U+0000)字符, 不要在这后面跟其它小数,因为 \0<digits> 是一个八进制转义序列。
[\b]匹配一个退格(U+0008)。(不要和\b混淆了。)
其他
字符用法
.1. (小数点)默认匹配除换行符之外的任何单个字符。
例如,/.n/ 将会匹配 "nay, an apple is on the tree" 中的'an''on',但是不会匹配 'nay'
2. 如果 ``s("dotAll")标志位被设为 true,它也会匹配换行符。
x|y匹配‘x’或者‘y’。

1.4 通过标志进行高级搜索

正则表达式有六个可选参数 (flags) 允许全局和不分大小写搜索等。这些参数既可以单独使用也能以任意顺序一起使用, 并且被包含在正则表达式实例中。

标志描述
g全局搜索
i不区分大小写搜索
m多行搜索
s允许.匹配换行符
u使用unicode码的模式进行匹配
y执行粘性(sticky)搜索,匹配从目标字符串的当前位置开始
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值