【JS】一文学会正则表达式
作者:mmseoamin日期:2024-01-23

1.概述

1.1什么是正则表达式

正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。

正则表通常被用来检索、替换那些符合某个模式(规则)的文本。

例如验证表单:

  • 用户名表单只能输入英文字母、数字或者下划线,
  • 昵称输入框中可以输入中文(匹配)。
  • 正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)。
  • 其他语言也会使用正则表达式,作为前端萌新,这里我主要是利用JavaScript 正则表达式完成验证。

    1.2 正则表达式的特点

    1. 灵活性、逻辑性和功能性非常的强。
    2. 可以迅速地用极简单的方式达到字符串的复杂控制。
    3. 对于刚接触的人来说,比较晦涩难懂。比如:/^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$/
    4. 实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达式. 比如用户名: /^[a-z0-9_-]{3,16}$/

    2. 使用

    2.1 创建

    在 JavaScript 中,可以通过两种方式创建一个正则表达式。

    方式一:通过调用RegExp对象的构造函数创建

    var regexp = new RegExp(/Syoyu/);
    console.log(regexp); // /Syoyu/
    

    方式二:利用字面量创建

     var rg = /Syoyu/;
    

    2.2 测试

    test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。

    var rg = /Syoyu/;
    console.log(rg.test(Syoyu));// 匹配字符中是否出现Syoyu  出现结果为true
    console.log(rg.test('Syoyu'));// 匹配字符中是否出现Syoyu 未出现结果为false
    

    3. 特殊字符

    3.1 组成

    一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。

    特殊字符非常多,可以参考:

    MDN

    jQuery 手册:正则表达式部分

    正则测试工具

    3.2 修饰符

    修饰符说明
    i不区分大小写搜索
    g全局搜索
    m多行搜索
    s允许 . 匹配换行符
    u使用unicode码的模式进行匹配
    y执行“粘性(sticky)”搜索,匹配从目标字符串的当前位置开始
    var reg = /pattern/flags // 使用方法
    

    3.3 边界符

    正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符

    边界符说明
    ^表示匹配行首的文本(以谁开始)
    $表示匹配行尾的文本(以谁结束)

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

    var rg = /Syoyu/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型
    // /Syoyu/ 只要包含有Syoyu这个字符串返回的都是true
    console.log(rg.test('Syoyu'))  // true
    console.log(rg.test('Syoyu1')) // true
    console.log(rg.test('Syoyu2')) // true
    var reg = /^Syoyu/; // 要求以Syoyu开头
    console.log(reg.test('Syoyu'));  // true
    console.log(reg.test('Syoyu'));  // true
    console.log(reg.test('SSyoyu')); // false
    var reg1 = /^Syoyu$/; // 精确匹配 要求必须是 Syoyu
    console.log(reg1.test('Syoyu'));      // true
    console.log(reg1.test('Syoyuu'));     // false
    console.log(reg1.test('SSyoyuu'));    // false
    console.log(reg1.test('SyoyuSyoyu')); // false
    

    3.4 字符类

    字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。

    3.4.1 [] 方括号

    表示有一系列字符可供选择,只要匹配其中一个就可以了

    var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
    console.log(rg.test('andy'));//true
    console.log(rg.test('baby'));//true
    console.log(rg.test('color'));//true
    console.log(rg.test('red'));//false
    var rg1 = /^[abc]$/; // 三选一 只有是a 或者是 b  或者是c 这三个字母才返回 true
    console.log(rg1.test('aa'));//false
    console.log(rg1.test('a'));//true
    console.log(rg1.test('b'));//true
    console.log(rg1.test('c'));//true
    console.log(rg1.test('abc'));//true
    var reg = /^[a-z]$/ //26个英文字母任何一个字母返回 true  - 表示的是a 到z 的范围  
    console.log(reg.test('a'));//true
    console.log(reg.test('z'));//true
    console.log(reg.test('A'));//false
    //字符组合
    var reg1 = /^[a-zA-Z0-9]$/; // 26个英文字母(大写和小写都可以)任何一个字母返回 true  
    //取反 方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。
    var reg2 = /^[^a-zA-Z0-9]$/;
    console.log(reg2.test('a'));//false
    console.log(reg2.test('B'));//false
    console.log(reg2.test(8));//false
    console.log(reg2.test('!'));//true
    
    3.4.2 量词符

    量词符用来设定某个模式出现的次数。

    量词说明
    *重复0次或更多次
    +重复1次或更多次
    ?重复0次或1次
    {n}重复n次
    {n,}重复n次或更多次
    {n,m}重复n到m次
    /^[a-zA-Z0-9_-]{6,16}$/ // 用户名只能为英文字母,数字,下划线或者短横线组成, 并且用户名长度为6~16位.
    
    3.4.3 括号总结
    1. 大括号 量词符. 里面表示重复次数

    2. 中括号 字符集合。匹配方括号中的任意字符.

    3. 小括号表示优先级

    正则表达式在线测试

    3.5 预定义类

    预定义类指的是某些常见模式的简写方式.

    var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/;
    var reg = /^\d{3,4}-\d{7,8}$/;
    
    预定义类说明
    \d匹配0-9的任一数字相当于[0-9]
    \D和\d相反, 匹配所有0-9以外的任一字符,相当于[^0-9]
    \w匹配任意的字母、数字和下划线,相当于[a-zA-Z0-9_]
    \W和\w相反,相当于[^a-zA-Z0-9_]
    \s匹配空格,相当于[\t\r\n\v\f]
    \S和\s相反,相当于[^\t\r\n\v\f]
    .查找单个字符,除了换行和行结束符
    \b匹配单词边界。
    \B匹配非单词边界。
    查找 NULL 字符。\n
    查找换行符\f
    查找换页符\r
    查找回车符\t
    查找制表符\v
    查找垂直制表符

    案例:验证座机号码

    4. 匹配方法

    方法

    4.1 regexp.exec(str)

    说明exec
    对字符串进行查找匹配,返回一个数组(未匹配到返回null)test
    对字符串进行测试匹配,返回true或falsereplace
    替换与正则表达式匹配的子串,返回替换后的值,原始值不变replaceAll
    match替换与正则表达式匹配的所有子串,返回替换后的值,原始值不变。
    注意: 当regexp是字符串时,匹配所有字串
    当regexp是正则表达式时,必须加上g修饰符,否则会报错
    找到一个或多个正则表达式的匹配, 它返回一个数组,在未匹配到时会返回 nullmatchAll
    search找到所有正则表达式的匹配, 它返回一个迭代器

    注意:和replaceAll类似

    检索与正则表达式相匹配的值,返回首次检索到的值的下标split
    把字符串分割为字符串数组
    let str = 'Syoyu'
    let regexp = /y/
    regexp.exec(str) // ['y', index: 1, input: 'Syoyu', groups: undefined]
    

    regexp.exec(str)方法返回匹配项

    4.2 regexp.test(str)

    注意:

    如果没有修饰符g,返回的就是匹配的第一项

    如果有g修饰符,返回第一个匹配项,并将紧随其后的位置保存在regexp.lastIndex中。下一次调用会从regexp.lastIndex位置匹配,返回下一个匹配项…

    let str = 'my name is Syoyu'
    let regexp = /y/g
    let result = null
    while(result = regexp.exec(str)) {
        console.log(result.index)
        // 1
        // 12
        // 14
    }
    
    let str = 'my name is Syoyu'
    let regexp = /Syoyu/
    let regexp1 = /syoyu/
    regexp.test(str) // true
    regexp1.test(str) // false
    

    查找匹配项,然后返回 true/false 表示是否存在

    4.3 str.replace(regexp)

    let str = 'my name is Syoyu'
    let regexp = /y/
    let regexp1 = /y/g
    str.replace(regexp, 'm') // mm name is Syoyu
    str.replace(regexp1, 'm') // mm name is Smomu
    console.log(str) // my name is Syoyu
    

    替换与正则表达式匹配的子串,返回替换后的值,原始值不变。在不设置全局匹配g的时候,只替换第一个匹配成功的字符串片段

    4.4 str.replaceAll(regexp)

    let str = 'my name is Syoyu'
    let regexp = /y/
    let regexp1 = /y/g
    str.replaceAll(regexp, 'm') // String.prototype.replaceAll called with a non-global RegExp argument
    str.replaceAll(regexp1, 'm') // mm name is Smomu
    str.replaceAll('y', 'm') // mm name is Smomu
    console.log(str) // my name is Syoyu
    

    替换与正则表达式匹配的所有子串,返回替换后的值,原始值不变。

    当regexp是字符串时,匹配所有字串;当regexp是正则表达式时,必须加上g修饰符,否则会报错

    4.5 str.match(regexp)

    let str = 'my name is Syoyu'
    let regexp = /y/
    let regexp1 = /y/g
    str.match(regexp) // ['y', index: 1, input: 'my name is Syoyu', groups: undefined]
    str.match(regexp1) // ['y', 'y', 'y']
    str.macth(/b/) // null
    

    找到一个或多个正则表达式的匹配, 它返回一个数组,在未匹配到时会返回 null

    如果 regexp 不带有 g 标记,则它以数组的形式返回第一个匹配项

    如果 regexp 带有 g 标记,则它将所有匹配项的数组作为字符串返回,而不包含分组和其他详细信息

    4.6 str.macthAll(regexp)

    let str = 'my name is Syoyu'
    let regexp = /y/
    let regexp1 = /y/g
    str.matchAll(regexp, 'm') // String.prototype.replaceAll called with a non-global RegExp argument
    str.matchAll(regexp1, 'm') // RegExpStringIterator {}
    [...str.matchAll(regexp1, 'm')] // [Array(1), Array(1), Array(1)]
    [...str.matchAll(regexp1, 'm')][0] // ['y', index: 1, input: 'my name is Syoyu', groups: undefined]
    [...str.matchAll(regexp1, 'm')][1] // ['y', index: 12, input: 'my name is Syoyu', groups: undefined]
    [...str.matchAll(regexp1, 'm')][2] // ['y', index: 14, input: 'my name is Syoyu', groups: undefined]
    

    返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

    当regexp是字符串时,匹配所有字串;当regexp是正则表达式时,必须加上g修饰符,否则会报错

    4.7 str.search(regexp)

    let str = 'my name is Syoyu'
    let regexp = /y/
    str.search(regexp) // 1 第一个匹配项的位置
    

    返回第一个匹配项的位置,如果未找到,则返回 -1

    4.8 str.split(regexp)

    let str = 'my name is Syoyu'
    let regexp = /\s/
    str.search(regexp) // ['my', 'name', 'is', 'Syoyu']
    

    使用正则表达式(或子字符串)作为分隔符来分割字符串

    5 几个常见的特性

    5.1 贪婪模式和非贪婪匹配

    匹配
    (1). 贪婪模式
    说明贪婪匹配量词符
    *, +, ?, {n,}, {n, m}非贪婪匹配量词符
    *?, +?, ??, {n,}?, {n, m}?
    let regexp = /my{1,3}name/
    

    尽可能多的匹配,例如下面的列子

    let str = 'my name is Syoyu'
    let regexp = /(\D{1,5})(\D+)/
    str.match(regexp)
    // ['my name is Syoyu', 'my na', 'me is Syoyu', index: 0, input: 'my name is Syoyu', groups: undefined]
    

    在匹配的时候,尝试匹配的顺序是从多往少的方向去匹配的。首先尝试yyy,然后再看整个正则能否匹配上,如果不能匹配上,则开始尝试yy,以此往复。

    如果多个贪婪量词挨在一起,则深度优先搜索

    (2). 非贪婪/懒惰模式

    其中,\D{1,5} 匹配的是’my na’,\D+ 匹配的是’me is Syoyu’

    let str = 'my name is Syoyu'
    let regexp = /(\D{1,5}?)(\D+?)/
    str.match(regexp) // ['my', 'm', 'y', index: 0, input: 'my name is Syoyu', groups: undefined]
    

    惰性量词就是在贪婪量词后面加个问号,表示尽可能少的匹配

    5.2 分组

    其中,\D{1,5}? 匹配的是 ‘m’,\D+? 匹配的是 'y’

    let str = 'Syoyu'
    let regexp = /(\D{1,2})(\D+)/
    str.replace(regexp, ' ') // 'oyu Sy'
    

    分组主要是用过()进行实现,Syoyu{3},是匹配u字母3次。而(Syoyu){3}是匹配Syoyu三次

    列入下面代码反向引用,巧用$分组捕获

    5.3 一些特殊的元字符

    上面代码中 Sy 和 oyu 交换了位置

    元字符
    说明(?:pattern)
    例如, 'industr(?:y|ies)‘就是一个比’industry|industries’ 更简略的表达式。(?=pattern)
    正向肯定预查(look ahead positive assert)。 例如,“Windows(?=95|98|NT|2000)“能匹配"Windows2000"中的"Windows”,但不能匹配"Windows3.1"中的"Windows”。(?!pattern)
    正向否定预查(negative assert)。 例如,“Windows(?!95|98|NT|2000)“能匹配"Windows3.1"中的"Windows”,但不能匹配"Windows2000"中的"Windows”。(?<=pattern)
    反向(look behind)肯定预查。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。(?
    反向否定预查。 例如"(?

    6 应用场景

    6.1 用户名校验-4到16位(字母,数字,下划线,减号)

    通过上面的内容,我们对正则表达式有了一定的了解

    下面我们来看一看正则表达式的一些实际应用案列:

    /^[a-zA-Z0-9_-]{4,16}$/
    
    6.2 手机号
    /^1[34578]\d{9}$/
    
    6.3 URL
    /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
    
    6.4 实现trim
    6.5 JavaScript实现千位分隔符
    String.prototype.trim = function() {
      return  this.replace(/^\s+|\s+$/g, '');
    }
    
    成都网站制作网页
    function numFormat(num) {
        return num.toString().replace(/\d+/, function(n) {
            retrun n.replace(/(\d)(?=(\d{3})+$)/g, function($1) {
                return $1 + ','
            })
        })
    }