zoukankan      html  css  js  c++  java
  • 全面的正则表达式文档

    概念

    正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

    var expression = / pattern / flags;

    创建正则(RegExp)

    var reg = /abc/g     // 字面量

    var reg = new RegExp('abc', 'g')   //构造函数方式

    正则定义了很多特殊意义的字符,有名词,量词,谓词等,下面逐一介绍

    简单字符

    没有特殊意义的字符都是简单字符,简单字符就代表自身

    /abc/    匹配 abc

    /123/    匹配 123

    /-_-/     匹配 -_-

    /2345/   匹配 2345

    转义字符

    是转移字符,其后面的字符会代表不同的意思,主要有三个作用:

    第一种,是为了匹配不方便显示的特殊字符,比如换行,tab符号等

    第二种,正则中预先定义了一些代表特殊意义的字符,比如w等

    第三种,在正则中某些字符有特殊含义,转义字符可以让其显示自身的含义

    元字符

    • . 匹配除换行符以外的任意字符
    • w 匹配字母或数字或下划线或汉字
    • s 匹配任意的空白符
    • d 匹配数字
    •  匹配单词的开始或结束
    • ^ 匹配字符串的开始
    • $ 匹配字符串的结束

    反义

    • W 匹配任意不是字母,数字,下划线,汉字的字符
    • S 匹配任意不是空白符的字符
    • D 匹配任意非数字的字符
    • B 匹配不是单词开头或结束的位置
    • [^x] 匹配除了x以外的任意字符
    • [^aeiou] 匹配除了aeiou这几个字母以外的任意字符

    字符边界

    有时我们会有边界的匹配要求,比如已xxx开头,已xxx结尾

    ^在[]外表示匹配开头的意思

    ^abc       // 可以匹配abc,但是不能匹配aabc

    $表示匹配结尾的意思

    abc$       // 可以匹配abc,但是不能匹配abcc

    上面提到的表示单词的边界

    abc      // 可以匹配 abc ,但是不能匹配 abcc

    量词

    如果需要匹配多次某个字符,正则也提供了量词的功能,正则中的量词有多个,如?、+、*、{n}、{m,n}、{m,}

    {n}匹配n次,比如a{2},匹配aa

    {m, n}匹配m-n次,优先匹配n次,比如a{1,3},可以匹配aaa、aa、a

    {m,}匹配m-∞次,优先匹配∞次,比如a{1,},可以匹配aaaa...

    ?匹配0次或1次,优先匹配1次,相当于{0,1}

    +匹配1-n次,优先匹配n次,相当于{1,}

    *匹配0-n次,优先匹配n次,相当于{0,}

    正则默认是贪婪模式的,凡是表示范围的量词,都优先匹配上限而不是下限

    a{1, 3} // 匹配字符串'aaa'的话,会匹配aaa而不是a

    有时候这不是我们想要的结果,可以在量词后面加上?,就可以开启非贪婪模式

    a{1, 3}? // 匹配字符串'aaa'的话,会匹配a而不是aaa

    懒惰与贪婪

    • *? 重复任意次,但尽可能少重复
    • +? 重复1次或更多次,但尽可能少重复
    • ?? 重复0次或1次,但尽可能少重复
    • {n,m}? 重复n到m次,但尽可能少重复
    • {n,}? 重复n次以上,但尽可能少重复

    字符集和

    有时我们需要匹配一类字符,字符集可以实现这个功能,字符集的语法用[]分隔

    下面的代码能够匹配a或b或c

    [abc]

    如果要表示字符很多,可以使用-表示一个范围内的字符,下面两个功能相同

    [0123456789]   =======         [0-9]

    在前面添加^,可表示非的意思,下面的代码能够匹配abc之外的任意字符

    [^abc]

    其实正则还内置了一些字符集,在上面的转义字符有提到,下面给出内置字符集对应的自定义字符集

    • . 匹配除了换行符( )以外的任意一个字符 ======  [^ ]
    • w = [0-9a-Z_]
    • W = [^0-9a-Z_]
    • s = [ v]
    • S = [^ v]
    • d = [0-9]
    • D = [^0-9]

    选择表达式

    有时我们想匹配x或者y,如果x和y是单个字符,可以使用字符集,[abc]可以匹配a或b或c,如果x和y是多个字符,字符集就无能为力了,此时就要用到分组

    正则中用|来表示分组选择,a|b表示匹配a或者b的意思

    123|456|789 // 匹配 123 或 456 或 789

    分组与引用

    分组是正则中非常强大的一个功能,分组可以让量词作用于一组字符,而非单个字符,分组的语法是圆括号包裹(xxx)

    (abc){2} // 匹配abcabc

    分组中还可以使用选择表达式

    (123|456){2} // 匹配 123123、456456、123456、456123

    和分组相关的概念还有一个捕获分组和非捕获分组,分组默认都是捕获的,在分组的(后面添加?:可以让分组变为非捕获分组,非捕获分组可以提高性能和简化逻辑

    '123'.match(/(?:123)/) // 返回 ['123']

    '123'.match(/(123)/)  // 返回 ['123', '123']

    和分组相关的另一个概念是引用,比如在匹配html标签时,通常希望<xxx></xxx>后面的xxx能够和前面保持一致

    引用的语法是数字,数字代表引用前面第几个捕获分组,注意非捕获分组不能被引用

    <([a-z]+)></1>            // 可以匹配 `<span></span>` 或 `<div></div>`等

    你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?<Word>w+)(或者 (?'Word'w+)),这样就把w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用k<Word>,所以上一个例子也可以写成这样:(?<Word>w+)s+k<Word>。

    零宽断言

    在使用正则表达式时,有时我们需要捕获的内容前后必须是特定内容,但又不捕获这些特定内容的时候,就要使用零宽断言;

    • (?=exp)断言自身出现的位置的后面能匹配表达式exp
    • (?<=exp)断言自身出现的位置的前面能匹配表达式exp
    • (?!exp) 断言此位置的后面不能匹配表达式exp
    • (?<!exp) 断言此位置的前面不能匹配表达式exp

    Egg:
    No zuo no die,no can no BB.

    w+(?=.)                  => ???

    (?<=o)s+w+               => ???

    w+s+(?![ndz])              => ???

    (?<!o)s+w+               => ???

    注释

    (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

    修饰符

    默认正则是区分大小写,这可能并不是我们想要的,正则提供了修饰符的功能,修复的语法如下

    /xxx/gi // 最后面的g和i就是两个修饰符

    g正则遇到第一个匹配的字符就会结束,加上全局修复符,可以让其匹配到结束

    i正则默认是区分大小写的,i可以忽略大小写

    m正则默认遇到换行符就结束了,不能匹配多行文本,m可以让其匹配多行文本

    api

    js中用到正则的地方有两个入口,正则的api和字符串的api

    • RegExp#test
    • RegExp#exec
    • String#search
    • String#match
    • String#split
    • String#replace

    RegExp#test

    每个正则实例都有test方法,test的参数是字符串,返回值是布尔值,表示当前正则是否能匹配指定的字符串
    /abc/.test('abc')      // true
    /abc/.test('abd')      // false

    RegExp#exec

    exec使用方法和test一样,只是返回值并不是布尔值,而是返回匹配的结果

    匹配成功返回一个数组,数组第一项是匹配结果,后面一次是捕获的分组

    /abc(d)/.exec('abcd') // ["abcd", "d", index: 0, input: "abcd"]

    此数组还有另外两个参数,input是输入的字符串,index表示匹配成功的序列在输入字符串中的索引位置

    如果有全局参数(g),第二次匹配时将从上次匹配结束时继续

    var r1 = /ab/

    r1.exec('ababab') // ['ab', index: 0]

    r1.exec('ababab') // ['ab', index: 0]

    var r2 = /ab/g

    r2.exec('ababab') // ['ab', index: 0]

    r2.exec('ababab') // ['ab', index: 2]

    r2.exec('ababab') // ['ab', index: 4]

    这一特性可以被用于循环匹配,比如统计字符串中abc的次数

    var reg = /abc/g

    var str = 'abcabcabcabcabc'

    var num = 0;

    var match = null;

    while((match = reg.exec(str)) !== null) {

        num++

    }

    console.log(num) // 5

    如果匹配失败则返回null

    /abc(d)/.exec('abc') // null

    String#search

    search方法返回匹配成功位置的索引,参数是字符串或正则,结果是索引

    'abc'.search(/abc/) // 0

    'abc'.search(/c/) // 2

    如果匹配失败则返回-1

    'abc'.search(/d/) // -1

    'abc'.search(/d/) !== -1 // false 转换为布尔值

    String#match

    match方法也会返回匹配的结果,匹配结果和exec类似

    'abc'.match(/abc/) // ['abc', index: 0, input: abc]

    'abc'.match(/abd/) // null

    如果有全局参数(g),match会返回所有的结果,并且没有index和input属性

    'abcabcabc'.match(/abc/g) // ['abc', 'abc', 'abc']

    String#split

    字符串的split方法,可以用指定符号分隔字符串,并返回数据

    'a,b,c'.split(',') // [a, b, c]

    其参数也可以使一个正则,如果分隔符有多个时,就必须使用正则

    'a,b.c'.split(/,|./) // [a, b, c]

    String#replace

    字符串的replace方法,可以将字符串的匹配字符,替换成另外的指定字符

    'abc'.replace('a', 'b') // 'bbc'

    其第一个参数可以是正则表达式,如果想全局替换需添加全局参数

    'abc'.replace(/[abc]/, 'y') // ybc

    'abc'.replace(/[abc]/g, 'y') // yyy 全局替换

    在第二个参数中,也可以引用前面匹配的结果

    'abc'.replace(/a/, '$&b') // abbc     $& 引用前面的匹配字符

    'abc'.replace(/(a)b/, '$1a') // aac   &n 引用前面匹配字符的分组

    'abc'.replace(/b/, '$\`') // aac      $` 引用匹配字符前面的字符

    'abc'.replace(/b/, "$'") // acc       $' 引用匹配字符后面的字符

    replace的第二个参数也可以是函数,其第一个参数是匹配内容,后面的参数是匹配的分组

    'abc'.replace(/w/g, function (match, $1, $2) {

        return match + '-'

    })

    // a-b-c-

    RegExp

    RegExp是一个全局函数,可以用来创建动态正则,其自身也有一些属性

    $_

    $n

    input

    length

    lastMatch

    来个例子

    /a(b)/.exec('abc') // ["ab", "b", index: 0, input: "abc"]

    RegExp.$_ // abc 上一次匹配的字符串

    RegExp.$1 // b 上一次匹配的捕获分组

    RegExp.input // abc 上一次匹配的字符串

    RegExp.lastMatch // ab 上一次匹配成功的字符

    RegExp.length // 2 上一次匹配的数组长度

    实例属性

    正则表达式的实例上也有一些属性

    flags

    ignoreCase

    global

    multiline

    source

    lastIndex

    egg:  var r = /abc/igm;

    r.flags // igm

    r.ignoreCase // true

    r.global // true

    r.multiline // true

    r.source // abc

    lastIndex比较有意思,表示上次匹配成功的是的索引

    var r = /abc/igm;

    r.exec('abcabcabc')

    r.lastIndex // 3

    r.exec('abcabcabc')

    r.lastIndex // 6

    可以更改lastIndex让其重新开始

    var r = /abc/igm;

    r.exec('abcabcabc') // ["abc", index: 0]

    r.exec('abcabcabc') // ["abc", index: 3]

    r.lastIndex = 0

    r.exec('abcabcabc') // ["abc", index: 0]

    实战实例

    来几个常用的例子

    /(?:0d{2-3}-)?d{7}/ // 电话号 010-xxx xxx

    /^1[378]d{9}$/ // 手机号 13xxx 17xxx 18xxx

    /^[0-9a-zA-Z_]+@[0-9a-zA-Z]+.[a-z]+$/ // 邮箱

    ((2[0-4]d|25[0-5]|[01]?dd?).){3}(2[0-4]d|25[0-5]|[01]?dd?) //ip地址

    str = str.replace(/^s*|s*$/g, '')    //trim()

     

    ^[u4e00-u9fa5]{0,}$                 //字符串仅能是中文。

     

    str = str.replace(/(.)1+/gi,'$1');          //字符串去重

    难点:捕获性分组与非捕获性分组

    捕获性分组工作模式()会把每个分组里匹配的值保存起来;

    非捕获性分组工作模式下分组(?:)会作为匹配校验,并出现在匹配结果字符里面,但不作为子匹配返回。

    比如利用非捕获性分组获取字符串000aaa111,而且只返回一个值为aaa111的数组:

    //先看用捕获性分组匹配会返回什么

    var str1 = '000aaa111';            

    var pattern = /([a-z]+)(d+)/; //捕获性分组匹配

    var arr = pattern.exec(str1); 

    console.log(arr) //['aaa111','aaa','111']   结果子串也获取到了,这并不是我们想要的结果

    //非捕获性分组

    var str2 = '000aaa111';

    var pattern2 = /(?:[a-z]+)(?:d+)/; //非捕获性分组匹配

    var arr2 = pattern.exec(str2); 

    console.log(arr2) //['aaa111']  结果正确


    比如利用捕获性分组把 hello world 互换成 world hello:

    方法一:通过exec函数

    var str = 'hello world';            //首先创建好字符串

    var pattern = /([a-z]+)s([a-z]+)/; //先通过正则匹配这个字符串,用分组模式来获取这两个单词

    var arr = pattern.exec(str); // exec方法返回的是一个数组,包含匹配到的字符串以及分组(也称子串)里的值

    console.log(arr); //['hello world','hello','world'] 

    console.log(arr[0]); //'hello world' 匹配到的字符串

    console.log(arr[1]); //'hello' 第一个分组([a-z]+)的值

    console.log(arr[2]); //'world' 第二个分组([a-z]+)的值

    //这时候两个分组的值都得到了,接下来用字符串拼接法实现互换

    var n_str = arr[2]+' '+arr[1];

    console.log(n_str) //world hello

    方法二:通过String的replace()

    var str = 'hello world';

    var pattern = /([a-z]+)s([a-z]+)/;

    var n_str = str.replace(pattern,"$2 $1"); //这里的$1、$2与方法二里的RegExp.$1、RegExp.$2作用是相同的。

    console.log(n_str) //world hello

    在线测试地址:https://regexr.com/

  • 相关阅读:
    commonjs
    基于webpack的vue开发环境搭建
    vs工程配置eslint检测环境
    h5笔记
    NPM install -save 和 -save-dev 区别
    小的div在大的div中垂直居中
    css position absolute相对于父元素的设置方式
    Python的支持工具[0] -> 环境包管理工具[1] -> Anaconda
    Python的支持工具[0] -> 环境包管理工具[0] -> pip
    代码编辑器[0] -> Vim/gVim[2] -> Vim 的相关知识
  • 原文地址:https://www.cnblogs.com/xuniannian/p/8524438.html
Copyright © 2011-2022 走看看