美文网首页
《JavaScript正则表达式迷你书》读书笔记

《JavaScript正则表达式迷你书》读书笔记

作者: roastwind | 来源:发表于2019-10-15 12:19 被阅读0次

    正则基础

    常见简写形式

    字符组 具体含义 记忆方式
    \d 表示 [0-9]。表示是一位数字。 其英文是 digit(数字)
    \D 表示 [^0-9]。表示除数字外的任意字符。
    \w 表示 [0-9a-zA-Z_]。表示数字、大小写字母和下划线。 w 是 word 的简写,也称单词字符。
    \W 表示 [^0-9a-zA-Z_]。非单词字符。
    \s 表示 [ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页 符。 s 是 space 的首字母,空白符的单词是 white space。
    \S 表示 [^ \t\v\n\r\f]。 非空白符。
    . 表示 [^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符 除外。 想想省略号 ... 中的每个点,都可以理解成占位符,表示任何类似的东西。
    [\d\D]/[\w\W]/[\s\S]/[^] 任意字符

    量词

    字符组 具体含义 记忆方式
    {m, } 至少出现m次
    {m} 等价于{m, m}, 出现m次
    ? 等价于{0, 1}, 出现或者不出现 问号的意思: 有么?
    + 等价于{1, }, 表示至少出现一次 加号是追加的意思,表示必须现有一个,然后才考虑追加
    * 等价于{0, }, 出现任意次, 有可能不出现 看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来

    修饰符

    字符组 具体含义 记忆方式
    g 匹配全局 单词是global
    i 忽略字母大小写 单词是ignoreCase
    m 多行匹配, 只影响 ^ 和 $,二者变成行的概念,即行开头和行结尾 单词是multiline

    匹配位置

    字符组 具体含义 记忆方式
    ^ (脱字符)匹配开头
    $ (美元符号)匹配结尾
    \b 单词边界 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置
    \B 非单词边界
    (?=p) "p"的子模式, "p" 前面的位置 positive lookahead(正向先行断言)
    (?!p) 非"p"前的位置 negative lookahead(负向先行断言)

    匹配开头与结尾

    • /^|$/g: 匹配列
    • /^|$/gm: 匹配行, m是既有修饰符

    ES5之后版本支持的位置判断

    • (?<=p): positive lookbehind(正向后行断言)
    • (?<!p): negative lookbehind(负向后行断言)

    举例

    • 千分位
      • 千分位示例1234567890
        • 三位数字的前面: /(?=\d{3}$)/g
          • (?=p): 正向先行断言
        • 多个三位数字: /(?=(\d{3})+$)/g
          • +: 量词, 多个
        • 最前面不匹配: /(?!^)(?=(\d{3})+$)/g
          • (?!^): 负向先行断言
        • 非捕获:
      • 带空格的千分位123456789 123456789
        • /(?!\b)(?=(\d{3})+\b)/g,即/(\B)(?=(\d{3})+\b)/g
        • ^$要替换成\b
      • 格式化千分位
        function format (num) {
          return num.toFixed(2).replace(/\B(?=(\d{3})+\b)/g, ",").replace(/^/, "$$ ");
        };
        console.log( format(1888) );
        // => "$ 1,888.00"
        
    • 验证密码: 长度为6~12位, 由数字、大写字母和小写字母,必须至少包含两种字符
      • 长度为6~12位的数字、大小写字母: /^[0-9a-zA-Z]{6, 12}$/
      • 至少包含两种
        • 解题核心
          • 至少包含一位数字字符: /(?=.*[0-9])/
        • 划分情况
          • 包含数字和大写字母
          • 包含数字和小写字母
          • 包含大写字母和小写字母
          • 包含数字、大写字母和小写字母
        • 正则: /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9a-zA-Z]{6,12}$/
          • 注意: {6,12}中间不能有空格
      • 不能同时全部为一种(反向思路)
        • 解题核心
          • 不能全部为一种字符(数字): /(?!^[0-9]{6,12}$)(^[0-9a-zA-Z]{6,12})/
        • 划分情况
          • 不能全为数字
          • 不能全为大写字母
          • 不能全为小写字母
        • 正则: /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)(^[0-9a-zA-Z]{6,12}$)/

    常用正则

    词法 含义 记忆方式
    (?=.[A-Z]) / (?=.?[A-Z]) / (.*[A-Z]) 至少包含一个大写字母

    括号的作用

    分组和分支

    • 分组: 'ababa abbb ababab'.match(/(ab)+/g)
    • 分支: (p1|p2)
      • 示例
        var regex = /^I love (JavaScript|Regular Expression)$/
        console.log(regex.test("I love JavaScript")) // true
        console.log(regex.test("I love Regular Expression")) // true
        

    分组引用: 正则引擎给分组开辟了一个空间, 用来存储每个分组匹配到的数据

    • 分组方法
      • 之前: /^d{4}-d{2}-d{2}$/
      • 之后: /^(d{4})-(d{2})-(d{2})$/
    • 提取数据:
      • match方法
        • 使用: '2017-06-12'.match(/(\d{4})-(\d{2})-(\d{2})/)
        • match返回数组: ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12", groups: undefined]
          • 第一个是整体匹配结果
          • 然后是各个分组匹配到的结果
          • 匹配下标
          • 输入的文本
          • 一个捕获组数组 或 undefined(如果没有定义命名捕获组)
        • 注意:
          • 使用g之后, 返回的是一个数组
            • 举例: '1s1'.match(/\d/g) => ["1", "1"]
          • 匹配不到, 则返回null
      • exec方法
        • 使用: /(\d{4})-(\d{2})-(\d{2})/.exec('2017-06-12')
      • 正常操作(test/match/exec)之后, 可以通过RegExp$0~$9获取
    • 替换: 转换yyyy-mm-dddd/mm/yyyy
      • $方法
        '2019-09-23'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1')
        
      • RegExp.$+function方法
        '2019-09-23'.replace(/(\d{4})-(\d{2})-(\d{2})/, function() {
            return `${RegExp.$2}/${RegExp.$3}/${RegExp.$1}`
        })
        
      • function方法
        '2019-09-23'.replace(/(\d{4})-(\d{2})-(\d{2})/, function(match, year, month, day) {
            return `${month}/${day}/${year}`
        })
        

    反向引用: \1

    • 同时匹配日期格式: 2019-09-24, 2019/09/24, 2019.09.24
      • 正则: /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/
      • 问题: 格式不统一, 2019/09-24同样能匹配
      • 解决方法: 使用反向引用
        • 正则: /\d{4}(-|\/|\.)\d{2}(\1)\d{2}/
      • 同理: \2是第二个分组引用, 依次类推
    • 括号嵌套
      • 正则
        // ["1231231233", "123", "1", "23", "3", index: 0, input: "1231231233", groups: undefined]
        1231231233'.match(/^((\d)(\d(\d)))\1\2\3\4$/) 
        
      • 拆分
        • /^((\d)(\d(\d)))$/.test('123') // true
        • /^(\d)$/.test('1') // true
        • /^(\d(\d))$/.test('23') // true
        • /^(\d)$/.test('3') // true, 同第二个一样
    • 引用不存在的分组
      • 会匹配反向引用本身
        • 正则: /\1/.test('\1') // true
    • 分组后由量词
      • 会匹配到最后一次匹配
        • 正则: '12345'.match(/(\d)+/) // ["12345", "5", index: 0, input: "12345", groups: undefined]
        • 对于反向引用, 同理: /(\d)+ \1/.test('12345 5') // true

    非捕获括号: (?:p)

    非捕获匹配到的值不会保存起来

    • 与之相反的是
      • 捕获型分组
      • 捕获型分支
    • 举例
      'ababa abbb ababab'.match(/(?:ab)+/g) // ["abab", "ab", "ababab"]
      var regex = /^I love (?:JavaScript|Regular Expression)$/
      regex.test("I love JavaScript") // true
      regex.test("I love Regular Expression") // true
              ```
      

    实战应用

    trim: 去掉头部和尾部的空字符串

    • js: ' hello '.trim() // 'hello'
    • 正则:
      • 匹配到开头和结尾的空字符串, 替换掉(效率高)
        '  hello '.replace(/^\s+|\s+$/g, '')
        
      • 惰性匹配*?, 匹配所有字符串, 提取相应数据
        '  hello '.replace(/^\s*(.*?)\s*$/, '$1') // 'hello'
        

    将每个单词的首字母转换为大写

    • 方法
      function titleize (str) {
          return str.toLowerCase().replace(/(?:^|\s)\w/g, function (c) {
              return c.toUpperCase();
          });
      }
      // My Name Is Epeli
      console.log( titleize('my name is epeli') )
      
    • 注意
      • \s: 制表符空格等, 用来匹配name这样的数据

    驼峰

    • 方法
      var camelize = function(str) {
          return str.replace(/[-_\s]+(.)?/g, function(match, c){
              return c ? c.toUpperCase() : ''
          })
      }
      console.log(camelize('-moz-transform'))
      
    • 注意
      • replace: 关于replace参数
        • match: 匹配到的字符串
        • p1,p2,...: 代表第n个括号匹配到的字符串
        • offset: 匹配到字符串在原字符串的偏移量
        • string: 被匹配的原字符串
        • NamedCaptureGroup: 命名捕获组匹配的对象
      • [-_\s]: 连字符、下划线和空白符
      • ?: 应对单词结尾不是单词字符, 例如'-moz-transform '

    中划线化

    • 方法
      var dasherize = function(str) {
          return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase()
      }
      console.log(dasherize('MozTTransform'))
      
    • 或者使用function
      var sperateLine = function(str) {
          return str.replace(/[A-Z]{1}/g, function(match) {
              return match ? `-${match.toLowerCase()}` : ''
          })
      }
      console.log(sperateLine('MozTTransform'))
      

    HTML转义和反转义

    • 转义
      • 方法
        var escapeHTML = function(str) {
            var escapeChars = {
                '<': 'lt',
                '>': 'gt',
                '"': 'quot',
                '&': 'amp',
                '\'': '#39'
            }
            return str.replace(new RegExp(`[${Object.keys(escapeChars).join('')}]`, 'g'), function(match) {
                return `&${escapeChars[match]};`
            })
        }
        console.log(escapeHTML('<div>hello, \'world\'</div>'))
        
    • 反转义
      • 方法
        var unescapeHTML = function(str) {
            var htmlEntities = {
                'lt': '<',
                'gt': '>',
                'quot': '"',
                'amp': '&',
                '#39': '\''
            }
            return str.replace(new RegExp(`\&([^;]+);`, 'g'), function(match, key) {
                console.log(match, key)
                return (key in htmlEntities) ? htmlEntities[key] : match
            })
        }
        console.log(unescapeHTML('&lt;div&gt;hello, &#39;world&#39;&lt;/div&gt;'))
        
      • 注意
        • 关于/\&([^;]+);/g^
          • 匹配输入字符串开头位置
          • 方括号中使用, 表示不接受该字符集合
          • 匹配^本身,使用\^
          • 图示: image.png
    • 成对标签
      • 简单匹配正则: /<([^>]+)>[\d\D]*<\/\1>/
      • 验证: /<([^>]+)>[\d\D]*<\/\1>/.test('<title>wrong!</p>') // false

    回溯法

    • 没有回溯的匹配
      • 举例: /ab{1,3}c/.test('abbbc')
    • 有回溯的匹配
      • 举例: /ab{1,3}c/.test('abbc')
    • 回溯产生的原因: 一次没有恰好匹配, 需要多次匹配
    • 建议: 应该避免
      • 举例
        // .*任意字符出现任意次, 会匹配到abc"de, 匹配完之后发现还有"正则, 然后会回溯,只匹配到abc
        /".*"/.test('"abc"de')
        // 更改建议, 匹配非"的字符任意次, 碰到"就终止匹配, 减少回溯, 提高效率
        /"[^"]*"/.test('"abc"de')
        
    • 释义
      • 百度百科

        回溯法也称试探法,它的基本思想是:从问题的某一种状态(初始状态)出发,搜索从这种状态出发 所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从 另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”、 不断“回溯”寻找解的方法,就称作“回溯法”。

      • 本质: 深度优先搜索算法

      • 回溯: 退到之前的某一步的过程

    • 常见的回溯形式
      • 贪婪量词
        • 尝试顺序从多往少的方向去尝试
        • 举例
          // ["12345", "123", "45", index: 0, input: "12345", groups: undefined]
          '12345'.match(/(\d{1,3})(\d{1,3})/)
          
      • 惰性量词
        • 贪婪量词后加问号尽可能少的匹配
        • 举例
          // ["1234", "1", "234", index: 0, input: "12345", groups: undefined]
          '12345'.match(/(\d{1,3}?)(\d{1,3})/)
          
        • 会回溯的惰性
          // ["12345", index: 0, input: "12345", groups: undefined]
          '12345'.match(/^\d{1,3}?\d{1,3}$/)
          
      • 分支结构
        • 分支也是惰性匹配
          // ["can", index: 0, input: "candy", groups: undefined]
          'candy'.match(/can|candy/)
          
        • 整体匹配的话,也会回溯
          // ["candy", index: 0, input: "candy", groups: undefined]
          'candy'.match(/^(?:can|candy)$/)
          
      • 总结
        • 回溯相对DFA引擎, 匹配效率低一些
        • 概念
          • DFA: 确定型有限自动机
          • NFA: 非确定型有限自动机
            • JavaScript就是, 而且流行, 匹配慢(相对DFA)
            • 流行原因: 编译快, 有趣?

    拆分

    • 结构的具体含义

      结构 说明 举例
      字面量 匹配一个具体字符,包含需要转义和不需要转义的 不转义: /a/匹配字符'a'; 转义: /\n/匹配换行符, \.匹配小数点
      字符组 匹配一个字符,可以是多种可能之一;反义字符组,表示除特定字符的其他字符 /[0-9]/表示匹配一个数字, 简写形式/\d/; [^0-9]表示匹配一个非数字, 简写形式/\D/
      量词 表示一个字符连续出现; 或常见的简写形式 /a{1,3}/表示a字符连续出现1~3次; /a+/表示a出现至少一次
      匹配一个位置, 而不是字符 ^表示匹配位置的开头, \b表示匹配单词边界, (?=\d)表示数字的前面
      分组 用括号表示一个整体 (ab)+表示ab两个字符连续出现多次, 也可以使用非捕获组(?:ab)+
      分支 多个子表达式多选一, 反向引用 `abc bcd表示匹配'abc'或者'bcd'字符子串;\2`表示引用第2个分组
    • 操作符(从上至下)

      操作符描述 操作符 优先级
      转义符 \ 1
      括号和方括号 (...)(?:...)(?=...)(?!...)[...] 2
      量词限定符 {m}{m,n}{m,}?*+ 3
      位置和序列 ^$\元字符一般字符 4
      管道符(竖杠) ` ` 5
      • 举例: /ab?(c|de*)+|fg/
    • 注意

      • 匹配字符串整体问题
        • 错误示例: /^abc|bcd$/
          QQ20191010-145021.png
        • 正确示例: /^(abc|bcd)$/
          QQ20191010-145048.png
        • 原因: 位置操作符优先级高于管道操作符
      • 量词连缀问题
        • 每个字符为'a','b','c'任选其一, 字符串长度是3的倍数
          • 错误示例: /^[abc]{3}+$/

            会报错, 说+前没有什么可重复的

          • 正确示例: /([abc]{3})+/

            QQ20191010-150526.png
      • 元字符转义问题
        • 元字符
          /\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,/.test('^$.*+?|\\/[]{}=!:-,')
          
        • 字符串中, 每个字符转义之后还是本身
          // true
          '^$.*+?|\\/[]{}=!:-,' === '\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,'
          
          • 不是每个字符都需要转义
            • 跟字符组有关的[]^-。因此在会引起歧义的地方进行转义, 例如^, 否则会把整个字符组看成反义字符组
              '^$.*+?|\\/[]{}=!:-,'.match(/[\^$.*+?|\\/\[\]{}=!:\-,]/g)
              
            • 匹配'[abc]'{3,5}
              /\[abc]/.test('[abc]')
              /\{3,5}/.test('{3,5}')
              /{,3}/.test('{,3}')
              
              • 原因: 只需要在第一个方括号转义即可,因为后面的方括号构不成字符组,正则不会引发歧义,自然不需要转义
            • 其他
              • =, !, :, -, ,不在特殊结构, 不需要转义
              • 括号前后需要转义, /\(123\)/
              • ^, $, ., *, +, ?, |, \, /等字符, 只要不在字符组内, 都需要转义
      • 案例分析
        • 正则: /^(\d{15}|\d{17}[\dxX])$/
        • IPV4正则: /^((0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])\.){3}(0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])$/
          • 结构: ((...)\.){3}(...)
          • 结构匹配: 3位数.3位数.3位数.3位数
          • 拆分
            • 0{0,2}\d: 匹配
              0~9
              // 或
              00~09
              // 或
              000~009
              
            • 0?\d{2}: 匹配
              10~99
              // 或
              010~099
              
            • 1\d{2}: 匹配100~199
            • 2[0-4]\d: 匹配200~249
            • 25[0-5]: 匹配250~255

    正则表达式构建

    • 平衡法则

      • 匹配预期的字符串
      • 不匹配非预期的字符串
      • 可读性和可维护性
      • 效率
    • 构建正则前提

      • 是否能使用正则
        • 举例: '1010010001...'
      • 是否有必要使用(复杂)正则
        • 字符串分隔举例
          var string = '2017-07-01'
          // 正则
          var reg = /^(\d{4})-(\d{2})-(\d{2})/
          console.log(string.match(reg))
          // js api
          var stringArray = string.split('-')
          console.log(stringArray)
          
        • 判断是否有问号
          var string = '?id=xx&act=search'
          // 正则
          console.log(string.search(/\?/))
          // js api
          console.log(string.indexOf('?'))
          
        • 获取子串
          var string = 'JavaScript'
          // 正则
          var reg = /.{4}(.+)/
          console.log(string.match(reg)[1])
          // js api
          console.log(string.substring(4))
          
        • 是否有必要构建一个复杂的正则
          • 密码问题: 长度6~12位, 由数字、小写和大写字符组成, 必须包含两种字符
          • 复杂正则: /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/
          • 分拆简单正则:
            var regex1 = /^[0-9a-zA-Z]{6,12}$/
            var regex2 = /^[0-9]$/
            var regex3 = /^[a-z]$/
            var regex4 = /^[A-Z]$/
            function checkPassword = function(string) {
                if (!regex1.test(string) return false
                if (regex2.test(string)) return false
                if (regex3.test(string)) return false
                if (regex4.test(string)) return false
                return true
            }
            
    • 准确性

      • 问题1:
        • 描述:匹配如下格式固话0551888888880551-88888888(0551)88888888
        • 分析(不考虑分机号和+86)
          • 区号, 以0开头的3~4位数字, 正则: /0\d{2,3}/
          • 号码, 非0开头的6~7位数字, 正则: /[1-9]\d{6,7}/
          • 匹配055188888888, 正则: /^0\d{2,3}[1-9]\d{6,7}$/
          • 匹配0551-88888888, 正则: /^0\d{2,3}-[1-9]\d{6,7}$/
          • 匹配(0551)88888888, 正则: /^\(0\d{2,3}\)[1-9]\d{6,7}$/
          • 合并正则: ^/0\d{2,3}[1-9]\d{6,7}|0\d{2,3}-[1-9]\d{6,7}|\(0\d{2,3}\)[1-9]\d{6,7}$/
          • 提取公共正则: /^(0\d{2,3}|0\d{2,3}-|\(0\d{2,3}\))[1-9]\d{6,7}$/
          • 进一步简写: /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/
            QQ20191011-121606.png
        • 测试
          /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/.test('055188888888')
          /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/.test('0551-88888888')
          /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/.test('(0551)88888888')
          /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/.test('051-8888888')
          
        • 问题
          • 3位和4位数字不一定是真实区号
      • 问题2:
        • 描述: 匹配如下格式浮点数1.23+1.23-1.2310+10-10.2+.2-.2
        • 分析
          • 符号部分: [+-]
          • 整数部分: \d+
          • 小数部分: \.\d+
          • 匹配1.23+1.23-1.23正则: /^[+-]?\d+\.\d+$/
          • 匹配10+10-10正则: /^[+-]?\d+$/
          • 匹配.2+.2-.2正则: /^[+-]?\.\d+$/
          • 合并正则: /^[+-]?(\d+\.\d+|\d+|\.\d+)$/
            float_1.png
          • 另外一种写法: /^[+-]?(\d+)?(\.)?\d+$/

            涉及到可维护性和可读性

    • 效率

      • 正则运行阶段

        • 编译

          引擎报错与否在这个阶段

        • 设定起始位置

        • 尝试匹配

          可以优化的阶段

        • 匹配失败,从下一位开始继续第3步

          可以优化的阶段

        • 最终结果: 匹配成功或失败

      • 运行代码示例

        var regex = /\d+/g;
        // 0 ["123", index: 0, input: "123abc34def", groups: undefined]
        console.log( regex.lastIndex, regex.exec("123abc34def") );
        // 3 ["34", index: 6, input: "123abc34def", groups: undefined]
        console.log( regex.lastIndex, regex.exec("123abc34def") );
        // 8 null
        console.log( regex.lastIndex, regex.exec("123abc34def") ); 
        // 0 ["123", index: 0, input: "123abc34def", groups: undefined]
        console.log( regex.lastIndex, regex.exec("123abc34def") );
        

        当使用testexec时, 正则有g时, 起始位置是从lastIndex开始的

      • 优化方法

        • 使用具体型字符组代替通配符, 来消除回溯

          • /".*"/.test('123"abc"456'): 回溯有4次
          • /".*?"/.test('123"abc"456'): 回溯有2次
            • 惰性匹配*?
          • /"[^"]*"/.test('123"abc"456'): 无回溯
        • 使用非捕获分组

          不需要使用分组引用和反向引用时

          • /^[+-]?(\d+\.\d+|\d+|\.\d+)$/=>/^[+-]?(?:\d+\.\d+|\d+|\.\d+)$/
        • 独立出确定字符

          • /a+/ => /aa*/
        • 提取公共分支部分

          可减少匹配过程中的重复

          • /^abc|^bcd/ => /^(abc|bcd)/
          • /this|that/ => /th(?:is|at)/
        • 减少分支的数量, 缩小它们范围

          • /red|read/ => /rea?d/: 可读性降低
      • 总结

        • 关于准确性: 满足需求即可
        • 关于效率: 看个人要求
        • 准确性思路
          • 针对每种情形, 分别写出正则
          • 再用分支进行合并
          • 提取公共部分

    正则表达式编程

    • 匹配: 目标字符串中是否有匹配的子串

    • 正则表达式的四种操作

      • 验证: 判断是否的操作
        • search
          // true
          !!~'abc123'.search(/\d/)
          
        • match
          // true
          !!'abc123'.match(/\d/)
          
        • test
          // true
          /\d/.test('abc123')
          
        • exec
          // true
          !!/\d/.exec('abc123')
          
      • 切分:
        • 切分,分隔的字符串
          // ["html", "js", "css"]
          'html,js,css'.split(/,/)
          
        • 日期切割
          // ["2019", "10", "11"]
          '2019/10/11'.split(/\D/)
          '2019.10.11'.split(/\D/)
          '2019-10-11'.split(/\D/)
          
      • 提取:
        • search
          '2019-10-11'.search(/^(\d{4})\D(\d{2})\D(\d{2})$/)
          // 2019 10 11
          console.log(RegExp.$1, RegExp.$2, RegExp.$3)
          
        • match
          // ["2019-10-11", "2019", "10", "11", index: 0, input: "2019-10-11", groups: undefined]
          '2019-10-11'.match(/^(\d{4})\D(\d{2})\D(\d{2})$/)
          
        • replace
          var date = []
          '2019-10-11'.replace(/^(\d{4})\D(\d{2})\D(\d{2})$/, function(year, month, day) {
              date.push(year, month, day)
          })
          // ["2019-10-11", "2019", "10"]
          console.log(date)
          
        • test
          /^(\d{4})\D(\d{2})\D(\d{2})$/.test('2019-10-11')
          // 2019 10 11
          console.log(RegExp.$1, RegExp.$2, RegExp.$3)
          
        • exec
          /^(\d{4})\D(\d{2})\D(\d{2})$/.exec('2019-10-11')
          // 2019 10 11
          console.log(RegExp.$1, RegExp.$2, RegExp.$3)
          
      • 替换: 需要重点掌握
        var tody = new Date('2019-10-11'.replace(/-/g, '/'))
        // Fri Oct 11 2019 00:00:00 GMT+0800 (China Standard Time)
        console.log(tody)
        
    • 相关API注意要点

      • searchmatch会把字符串转成正则

        // 0
        '2019.10.11'.search('.')
        // ["2", index: 0, input: "2019.10.11", groups: undefined]
        '2019.10.11'.match('.')
        // 4
        '2019.10.11'.search('\\.')
        // [".", index: 4, input: "2019.10.11", groups: undefined]
        '2019.10.11'.match('\\.')
        // 4
        '2019.10.11'.search(/\./)
        // [".", index: 4, input: "2019.10.11", groups: undefined]
        '2019.10.11'.match(/\./)
        // "2019/10.11"
        '2019.10.11'.replace('.', '/')
        
      • match返回的格式问题: 与是否有修饰符g有关

        // ["2019", "2019", index: 0, input: "2019.10.11", groups: undefined]
        console.log('2019.10.11'.match(/\b(\d+)\b/))
        // ["2019", "10", "11"]
        console.log('2019.10.11'.match(/\b(\d+)\b/g))
        
      • execmatch更强大: match使用g之后, 没有关键信息index, exec可以解决这个问题, 并且接着上一次继续匹配

        var string = "2019.10.11";
        var regex2 = /\b(\d+)\b/g;
        // ["2019", "2019", index: 0, input: "2019.10.11", groups: undefined]
        console.log( regex2.exec(string) );
        // 4
        console.log( regex2.lastIndex);
        // ["10", "10", index: 5, input: "2019.10.11", groups: undefined]
        console.log( regex2.exec(string) );
        // 7
        console.log( regex2.lastIndex);
        // ["11", "11", index: 8, input: "2019.10.11", groups: undefined]
        console.log( regex2.exec(string) );
        // 10
        console.log( regex2.lastIndex);
        // null
        console.log( regex2.exec(string) );
        // 0
        console.log( regex2.lastIndex);
        
        • 对使用exec用法优化

          示例中lastIndex表示下次匹配的开始位置

          var string = '2019.10.11'
          var regex = /\b(\d+)\b/g
          var result
          while (result = regex.exec(string)) {
              console.log(result, regex.lastIndex)
          }
          
      • 修饰符gexectest的影响

        • 字符串的四个方法, 每次匹配都是从0开始, 即lastIndex属性始终不变
          var regex = /a/g
          'a'.search(regex)
          // 0
          console.log(regex.lastIndex)
          'ab'.search(regex)
          // 0
          console.log(regex.lastIndex)
          
        • 正则的exectest方法, 当正则含有g, 每次匹配都会更改lastIndex; 不含g, 则不会改变lastIndex
          var regex = /a/g
          // true 1
          console.log( regex.test('a'), regex.lastIndex )
          // true 3
          console.log( regex.test('abac'), regex.lastIndex )
          // false 0
          console.log( regex.test('abacd'), regex.lastIndex )
          
      • test整体匹配时需要^$

        test是看目标字符串中是否有子串符合条件

        // true
        /123/.test('a123b')
        // false
        /^123$/.test('a123b')
        // true
        /^123$/.test('123')
        
    • split相关事项

      • 有2个参数, 第2个表示数组最大长度
        // ["js", "css"]
        'js,css,html'.split(/,/, 2)
        
      • 使用分组, 则结果包含分隔符本身
        // ["js", ",", "css", ",", "html"]
        'js,css,html'.split(/(,)/)
        
    • replace很强大

      • 第二个参数是字符串时, 有如下含义

        属性 描述
        1,2,...,$99 匹配第1~99个分组里捕获到的文本
        $& 匹配到的子串文本
        $` 匹配到的子串左边的文本
        $' 匹配到的子串右边的文本
        $$ 美元符号
        • 2,3,5变成5=2+3
          // "5=2+3"
          '2,3,5'.replace(/(\d+),(\d+),(\d+)/, '$3=$1+$2')
          
        • 2,3,5变成222,333,555
          '2,3,5'.replace(/(\d+)/g, '$&$&$&')
          
        • 2+3=5变成2+3=2+3=5=5
          // "2+3=2+3=5=5"
          '2+3=5'.replace(/(=)/, "$&$`$&$'$&")
          
      • 第二个参数是函数时

        replace此时拿到的信息比exec

        // ["1234", "1", "4", 0, "1234 2345 3456"]
        // ["2345", "2", "5", 5, "1234 2345 3456"]
        // ["3456", "3", "6", 10, "1234 2345 3456"]
        "1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) { 
            // $1是每组数字的开始
            // $2是每组数字的结束
            console.log([match, $1, $2, index, input]);
        })
        
    • 使用构造函数需要注意的问题

      不推荐使用, 会写很多的\

      // ["2017-06-27", "2017.06.27", "2017/06/27"]
      '2017-06-27 2017.06.27 2017/06/27'.match(/\d{4}(-|\.|\/)\d{2}\1\d{2}/g)
      // ["2017-06-27", "2017.06.27", "2017/06/27"]
      '2017-06-27 2017.06.27 2017/06/27'.match(new RegExp('\\d{4}(-|\\.|\\/)\\d{2}\\1\\d{2}', 'g'))
      
    • 修饰符

      修饰符 描述 单词
      /g/ 全局匹配(找到所有的) global
      /i/ 忽略字母大小写 ignoreCase
      /m/ 多行匹配, 只影响^$, 二者变成行概念(行开头、行结尾) multiline
      • 只读属性
        var regex = /\w/img
        // true
        console.log(regex.global)
        // true
        console.log(regex.ignoreCase)
        // true
        console.log(regex.multiline)
        
    • source属性

      对象属性, 除了global, ignoreCase, multiline, lastIndex还有source属性; 用来构建动态正则, 或确认真正的正则

      var className = "high";
      // => (^|\s)high(\s|$) 
      // => 即字符串"(^|\\s)high(\\s|$)"
      var regex = new RegExp("(^|\\s)" + className + "(\\s|$)"); console.log( regex.source )
      console.log(regex.test(' high '), regex.test('high'))
      
    • 构造函数属性

      静态属性随着最后一次正则操作而变化, 除了$1,...$9, 还有几个不太常用的(有兼容问题)

      静态属性 描述 简写形式
      RegExp.input 最近一次目标字符串 RegExp['$_']
      RegExp.lastMatch 最近一次匹配的文本 RegExp['$&']
      RegExp.lastParen 最近一次捕获的文本 RegExp['$+']
      RegExp.leftContext 目标匹配lastMatch之前的文本 RegExp['$`']
      RegExp.rightContext 目标匹配lastMatch之后的文本 RegExp["$'"]
      var regex = /([abc])(\d)/g
      var string = 'a1b2c3d4e5'
      string.match(regex)
      
      // a1b2c3d4e5 a1b2c3d4e5
      console.log(RegExp.input, RegExp['$_'])
      // c3 c3
      console.log(RegExp.lastMatch, RegExp['$&'])
      // 3 3
      console.log(RegExp.lastParen, RegExp['$+'])
      // a1b2 c3
      console.log(RegExp.leftContext, RegExp['$&'])
      // d4e5 d4e5
      console.log(RegExp.rightContext, RegExp["$'"])
          ```
      

    真实案例

    • 构造函数生成正则: 通过class获取dom
      function getClassByName(className) {
          var elements = document.getElementByTagName('*')
          var regex = new RegExp('(^|\\s)' + className + '($|\\s)')
          var result = []
          var elementsLength = elements.length
          for (var i = 0; i < elementsLength; i++) {
              var element = elements[i]
              if (regex.test(element.className)) {
                  result.push(element)
              }
          }
          return result
      }
      
    • 字符串保存数据: 判断数据类型
      var utils = {}
      'Boolean|Number|String|Function|Array|Date|RegExp|Object|Error'.split('|').forEach(function(item) {
          utils['is' + item] = function(obj) {
              return {}.toString.call(obj) === '[object '+ item +']'
          }
      })
      console.log(utils.isArray([1, 2, 3]))
      
    • 正则替代 &&(不兼容IE)
      var readyRegex = /complete|loaded|interactive/
      function ready(callback) {
          if (readyRegex.test(document.readyState) && document.body) {
              callback()
          }
          else {
              document.addEventListener('DomContentLoaded', function() {
                  callback()
              }, false)
          }
      }
      
    • 强大的replace: 参数查询压缩
      function compress(source) {
          var keys = {}
          // [^=&]中的&不能去掉, 否则第二次会匹配成&b=2
          // key不能为空, value有可能为空, 所以第一次是+, 第二次是*
          source.replace(/([^=&]+)=([^&])*/g, function(full, key, value) {
              keys[key] = (keys[key] ? keys[key] + ',' : '') + value
          })
          var result = []
          for (var key in keys) {
              result.push(key + '=' + keys[key])
          }
          return result.join('&')
      }
      // a=1,3&b=2,4
      console.log(compress('a=1&b=2&a=3&b=4'))
      
    • 根据字符串生成正则
      function createRegex(regex) {
          try {
              if (regex[0] === '/') {
                  regex = regex.split('/')
                  regex.shift()
                  var flags = regex.pop()
                  regex = regex.join('/')
                  regex = new RegExp(regex, flags)
              }
              else {
                  regex = new RegExp(regex, 'g')
              }
              return {
                  success: true,
                  result: regex
              }
          }
          catch (e) {
              return {
                  success: false,
                  message: '无效的正则表达式'
              }
          }
      }
      // {success: true, result: /\d/gm}
      console.log(createRegex('/\\d/gm'))
      // {success: true, result: /\d/g}
      console.log(createRegex('/\\d/g'))
      

    参考资料

    相关文章

      网友评论

          本文标题:《JavaScript正则表达式迷你书》读书笔记

          本文链接:https://www.haomeiwen.com/subject/aotsmctx.html