学习笔记之JS正则

799 阅读5分钟

前言

正则知识架构

  • 字符匹配
    1. 两种模糊匹配
      • 横向模糊匹配: 匹配一个长度不确定的分组或者字符
      • 纵向模糊匹配: 匹配一个不确定内容的字符
    2. 字符组
      • 字符组(字符类),用以实现纵向模糊匹配.
      • 意义是匹配字符组中的字符之一.
      • 案例: [abc]表示匹配"a""b""c"其中之一.
      • 由于"-"在字符组中可以连接两个字符表示连续的字符序列,所以我们需要注意转义.如:
        • [-az][az-][a\-z].
    3. 量词
      • 量词,用以实现横向模糊匹配.
      • 意义是描述字符或者分组的重复出现次数.
      • 量词的匹配模式分为:贪婪模式惰性模式:
        • 贪婪模式(默认): 在量词区间内,尽可能多匹配.
        • 惰性模式: 在量词区间内,尽可能少匹配.
          • 开启:在量词后面加?.
          • 注意:这个?是修饰量词的.
        • 贪婪量词: {m,n}.
        • 惰性量词: {m,n}?.
    4. 分支结构
      • 支持多个子模式任选其一,使用|(管道符)分隔多个子模式.
      • 注意分支结构内部内容使用()与外部分离,如:(p1|p2|p3).
      • 分支结构中的匹配优先级: 从前往后.
        • 比如我用/good|goodbye/,去匹配"goodbye"字符串时,结果是"good".
  • 位置匹配
    1. 什么是位置?
      • 相邻字符之间的位置.
    2. 如何匹配位置?
      • 使用字符锚.
    3. 位置的特性
      • 对于位置的理解,我们可以理解空字符"",如:介于字符之间,介于开头结尾之间.
      • 直观展示:
        • "hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "o" + ""
  • 括号的作用
    1. 分组与分支结构
      • 用括号隔离分组内容和外部内容,如/(ab)+/g.
      • 用括号隔离分支结构内部内容与外部内容,如(p1|p2).
    2. 捕获分组
      • 我们可以用$n捕获分组.
    3. 反向引用
      • 使用\1反向引用分组.
      • 如果没有则翻译成转义字符.
    4. 非捕获分组
      • 不可被捕获,无法反向引用,表达方式(?:p).
  • 回溯法原理
    1. 没有回溯的匹配
      • 假设: 用/ab{1,3}c/去匹配"abbbc"则不会发生回溯.
    2. 有回溯的匹配
      • 假设: 用/ab{1,3}c/去匹配"abbc"则不会发生回溯.
      • 本质上是由贪婪模式的特性引起的.
      • 解决方案:
        1. 使用惰性模式(当然惰性模式中也会有溯回).
        2. 使用[^"](假设匹配引号内部的内容),排除边界字符.
    3. 常见的回溯形式
      • 我们知道:在深度优先搜索算法中,退回到之前某一步的这个过程,我们将其称为回溯.
      • 即,也就是尝试匹配失败时,接下来的一步通常就是回溯.
      • 贪婪量词,惰性量词,分支结构这三个场景都会存在溯回.因为都属于暧昧的表达方式.
  • 拆分
    1. 结构和操作符
      • 字符字面量.
      • 字符组.
      • 量词.
      • 锚字符.
      • 分组.
      • 选择分支.
      • 反向引用.
    2. 注意要点
      • 匹配字符串整体问题,如:/^abc|bcd$/(匹配不符合预期)=>/^(abc|bcd)$/.
      • 量词连缀问题,如:/[abc]{3}+/(不合法)=>/([abc]{3})+/.
      • 元字符转义问题,如:"^$.*+?|\\/[]{}=!:-,"对应的正则/\^\$\.\*\+\?\|\\\/\[\]\{\}\=\=\!\:\-\,/
        • 元字符一律可以转义.
        • 字符组中必要转义,如[],^,-,
        • 简要转义/\[abc\]/=>/\[abc]/(两者等价),/\{3,5}/同理.
        • {,n}是一个不合法的量词,会被当做普通字符串(换言之,不用转义).
        • =,+,:,-,,等符号,只要不在特殊结构中,也不需要转义.
        • 括号前后都需要转移的,如/\(123\)/.
        • ^,$,.,*,+,?,|,\,/等字符,只要不在字符组内,都需要转义的.
  • 构建(方法论)
    1. 平衡法则.
      • 匹配预期的字符串.
      • 不匹配非预期的字符串.
      • 可读性和可维护性.
      • 效率.
    2. 构建正则前提.
      • 能否使用正则.
      • 是否有必要使用使用正则.如:
        • 拆分字符串可以使用split代劳.
        • 判断是否存在某字符使用search,indexOf代劳.
      • 是否有必要构建一个复杂的正则
        • 对于选择分支,我们可以使用js条件语句代劳.以拆分正则.
    3. 准确性.
      • 匹配预期的字符串.
      • 不匹配非预期的字符串.
    4. 效率.
      • 优化方式:
        • 使用具体型字符来代替通配符,来消除回溯.
        • 使用非捕获型分组.
        • 独立出确定字符.
        • 提取分支公共部分.
        • 减少分支数量,缩小它们的范围.
  • 编译
    1. 正则表达式的四种操作
      • 分别有:验证,切分,提取,替换.
      • 验证: 换言之,就是需要返回一个boolean.
        • search: 通过将返回的哨位值-1转换成0,再!!转成boolean.
        • test: 通过返回的boolean判定(如果没有^$限定,则存在即可).
        • match: 返回值有两种可能一种是null,另一种是数组,可以直接!!转换成boolean.
        • exec: 和match同理.
      • 切分: 按照一定的规则切分字符串.
        • split: 如,2017/06/26".split(/\D/).
      • 提取: 使用分组捕获功能,提取部分匹配的数据.
        • match: 通过返回的数据提取.
          • 数据结构: [match,$1,$2,$3,index:_index,input:_input].
        • exec: 与match同理.
        • test: 调用test之后,通过构造函数属性RegExp.$1,RegExp.$2...获取.
        • search: 与test同理.
        • replace: 通过Array.prototype.push抛出要提取的数据.
      • 替换: 替换匹配字符.
        • replace: 把日期的横杠换成斜杠(把横杠换成斜杠的原因就是,我们需要new一个日期).
    2. 相关API注意要点
      • 明确API的所属对象,如:
        • search,split,match,replace: 属于字符串(String#)实例方法.
        • test,exec: 属于正则(RegExp#)实例方法.
      • 注意参数问题,如:
        • 字符串4个实例方法,即search,split,match,replace都支持正则和字符串.
        • searchmatch: 会把字符串直接转换为正则,也就是说,有必要的时候我们需要进行转义.
        • splitreplace: 则会作为字面量自动转义,也就是说,没有必要进行手动转义.
      • matchexec之间的不同:
        • 不带g的情况下,matchexec都会返回完整的匹配信息.
        • g的情况下:
          • match: 会将所有完整正则匹配的内容返回,但是不会返回完整信息.
          • exec: 每调用一次会返回一条完整信息,依次推移,我们通过RegExp.lastIndex获取到下一次开始的位置,如果是0,我们就停止.(while).
        • 引申testexec一样,带g之后,如果调用多次,就会RegExp.lastIndex推移,而且test最后的返回值肯定是false.
      • test整体匹配需要^$.
      • split:
        • 第二个参数决定返回数组的最大长度.
        • 如果正则是加入分组/(,)/,则结果包含分隔符["999",",","999",",","999"].
      • replace:
        • 当replace第二个参数是字符串的时候,如下字符有特殊的含义.
          • $1,$2,...,$99匹配第1~99个分组里捕获的文本
          • $&匹配到的子串文本
          • $\匹配到的子串的左边文本
          • $'匹配到的子串的右边文本
          • ?美元符号
        • 当replace的第二个参数是函数的时候,其回调函数传入这些值:
          • (match,$1,$2,$3,index,input).
        • replace带g调用多次的时候会出现index偏移的效果.
      • 不推荐使用构造函数,推荐使用字面量,省代码.且不执行的时候更为轻量级.
      • 修饰符
        • ES5中修饰符,共3个:
          1. g(global):全局匹配.
          2. i(ignoreCase):忽略大小写.
          3. m(multiline):多行匹配,只影响^$,两者将会变成行开头和行结尾.
      • 正则实例对象的只读属性.
        • regex.global: 是否为全局配.
        • regex.ignoreCase: 是否忽略大小写.
        • regex.multiline: 是否为多行匹配.
        • regex.lastIndex: 下一个开始匹配的位置.
        • regex.source: 获取构造函数构造出来的成品的字符串.如:"(^|\\s)high(\\s|$)".
      • 构造函数的静态属性.
        • RegExp.input: 最近一次目标字符串,简写RegExp["$_"].
        • RegExp.lastMatch: 最近一次匹配的文本,简写RegExp["$&"].
        • RegExp.lastParen: 最近一次捕获的文本,简写RegExp["$+"].
        • RegExp.leftContext: 目标字符串中lastMatch之前的文本,简写成RegExp["$"]``.
        • RegExp.rightContext: 目标字符串中lastMatch之后的文本,简写成RegExp["$'"].

归类

常见字符组简写形式

  • \d(digit)就是[0-9]:匹配数字字符.
  • \D就是[0-9]:匹配除了数字以外的任意字符.
  • \w(word)就是[0-9A-Za-z_]: 匹配单词字符,包括:数字,大小写字母,下划线.
  • \W就是[^0-9A-Za-z_]:匹配非单词字符.
  • \s(space character)就是[\t\v\n\r\f]匹配空白符,包括:空格,水平制表符,垂直制表符,换行符,回车符,换页符.
  • \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次或没有出现.
  • +:等价于{1,},表示至少出现1次.
  • *:等价于{0,}表示可以出现人一次,或者没有出现.

字符锚点

在es5中,共有6个锚字符: ^ $ \b \B (?=p) (?!p)

  • ^$:
    • ^(脱字符),匹配开头.在多行模式中匹配行开头.
    • $(美元符号),匹配结尾,多行模式中匹配行结尾.
  • \b\B:
    • \b指的是单词边界,也就是\w与\W(或^或$)之间.
    • \B指的是非单词边界,也就是\w\w之间,\W\W之间,\W^之间,\W$之间.
  • (?=p)(?!p):
    • p指的是一个子模式.
    • (?=p)的学名是正向先行断言(positive lookahead).
      • 匹配子模式的前面位置.
    • (?!p)的学名是负向先行断言(negative lookahead).
      • 匹配非(子模式的前面位置).
    • (?<=p)的学名是正向后继断言(positive lookbehind).
      • 匹配子模式的后面位置.
    • (?<!p)的学名是负向后继断言(negative lookbehind).
      • 匹配非(子模式的后面位置).

表达式结构

  • 字面量: 匹配一个具体字符(注意特殊符号转义).
  • 字符组: 匹配一个字符的多种可能之一.如:[0-9],\d.
  • 量词: 匹配字符连续出现的次数.
  • 锚点: 一个位置,而不是字符.
  • 分组: 匹配一个整体,如(ab)+,非捕获(?:ab)+.
  • 分支: 匹配多个子表达式之一.
  • 反向引用: 引用出现过分组,如\2.

表达式操作符

  • 转义符: \.
  • 括号和方括号: (),(?:),(?=),(?!),[].
  • 量词限定符: {m},{m,n},{m,},?,*,+.
  • 位置和序列: ^,$,\,\元字符,一般字符.
  • 管道符: |

案例

回溯图

  • 贪婪量词
var string = "12345";
var regex = /(\d{1,3})(\d{1,3})/;
console.log( string.match(regex) );
// => ["12345", "123", "45", index: 0, input: "12345"]
  • 惰性量词
var string = "12345";
var regex = /(\d{1,3}?)(\d{1,3})/;
console.log( string.match(regex) );
// => ["1234", "1", "234", index: 0, input: "12345"]
RegExp: /^\d{1,3}?\d{1,3}$/

*__Begin!___Digit___Digit___End!__*
           |_____| |_____|
        1 to 3 times 1 to 3 times
  • 分支结构
RegExp: /^(?:can|candy)$/

            __can__
*__Begin!__|       |__end!__*
           |_candy_|

元字符转义测试

var string = "^$.*+?|\\/[]{}=!:-,";
var regex = /\^\$\.\*\+\?\|\\\/\[\]\{\}\=\=\!\:\-\,/
console.log(regex.test(string));
// => true
var string = "^$.*+?|\\/[]{}=!:-,"
var stirng2 = "\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,"
console.log(string === string2)
// => true