正则表达式高级进阶

概述

本文主要经过介绍正则表达式中的一些进阶内容,让读者了解正则表达式在平常使用中用到的比较少可是又比较重要的一部份内容,从而让你们对正则表达式有一个更加深入的认识。javascript

本文的主要内容为:java

  • 正则表达式回溯法原理
  • 正则表达式操做符优先级

本文不介绍相关正则表达式的基本用法,若是对正则表达式的基本使用方法还不了解的同窗,能够阅读个人上一篇博客——正则表达式语法入门正则表达式

回溯法原理

回溯是影响正则表达式效率的一个很是重要的缘由,咱们在进行正则表达式匹配时,必定要尽量的避免回溯。算法

不少人可能只是对据说过“回溯法”,并不了解其中具体内容和原理,接下来就先让咱们看下什么是回溯法。post

回溯法的定义

回溯法就是指正则表达式从头开始依次进行匹配,若是匹配到某个特定的状况下时,发现没法继续进行匹配,须要回退到以前匹配的结果,选择另外一个分支继续进行匹配中的现象。这个描述可能有点抽象,咱们举一个简单的例子,让你们可以更加明确的理解回溯法:网站

const reg = /ab{1,3}c/;

const str = 'abbc';

// 第1步:匹配/a/,获得'a'
// 第2步:匹配/ab{1}/,获得'ab'
// 第3步:匹配/ab{2}/,获得'abb'
// 第4步:匹配/ab{3}/,匹配失败,须要进行回溯
// 第5步:回溯到/ab{2}/,继续匹配/ab{2}c/,获得'abbc'
// 第6步:正则表达式匹配完成,获得'abbc'

若是咱们把正则表达式的各个分支都整理成一棵树的话,正则表达式的匹配其实就是一个深度优先搜索算法。而回溯其实就是在进行深度优先匹配失败后的后退正常操做逻辑。spa

若是退回到了根节点仍然没法匹配的话,就会将index向后移动一位,从新构建匹配数。即/bc/'abc'时,因为第一个字符'a'没法匹配,则移动到'b'开始匹配。3d

回溯法产生场景

理解了回溯法和回溯操做,接下来咱们来看下什么场景下会出现回溯。出现回溯的场景主要有如下几种:code

  1. 贪婪量词(贪婪匹配)
  2. 惰性量词(非贪婪匹配)
  3. 分支结构(分支匹配)

接下来,让咱们一个一个来看下这些场景是如何出现回溯的。blog

贪婪量词(贪婪匹配)

const reg = /ab{1,3}c/;

const str = 'abbc';

// 第1步:匹配/a/,获得'a'
// 第2步:匹配/ab{1}/,获得'ab'
// 第3步:匹配/ab{2}/,获得'abb'
// 第4步:匹配/ab{3}/,匹配失败,须要进行回溯
// 第5步:回溯到/ab{2}/,继续匹配/ab{2}c/,获得'abbc'
// 第6步:正则表达式匹配完成,获得'abbc'

最开始的例子其实就是一个贪婪匹配的示例,经过尽量多的匹配b从而致使了回溯。

惰性量词(非贪婪匹配)

const reg = /ab{1,3}?c/;

const str = 'abbc';

// 第1步:匹配/a/,获得'a'
// 第2步:匹配/ab{1}/,获得'ab'
// 第3步:匹配/ab{1}c/,匹配失败,须要进行回溯
// 第4步:回溯到/ab{1}/,继续匹配/ab{2}/,获得'abb'
// 第5步:匹配/ab{2}c/,获得'abbc'
// 第6步:正则表达式匹配完成,获得'abbc'

与贪婪匹配相似,非贪婪匹配虽然每次都是去最小匹配数目,可是也会出现回溯的状况。

分支结构(分支匹配)

const reg = /(ab|abc)d/;

const str = 'abcd';

// 第1步:匹配/ab/,获得'ab'
// 第2步:匹配/abd/,匹配失败,须要进行回溯
// 第3步:回溯到//,继续匹配/abc/,获得'abc'
// 第4步:匹配/abcd/,获得'abcd'
// 第5步:正则表达式匹配完成,获得'abcd'

经过上面的示例咱们能够看到,分支结构在出现两个分支状况相似的时候,也会出现回溯的状况,在这种状况下,若是一个分支没法匹配,则会回到这个分支的最初状况来从新进行匹配。

正则表达式操做符优先级

看完了回溯法,下面咱们来了解下关于正则表达式操做符的优先级。

咱们直接看结论,而后再根据结论来给你们提供示例进行理解。

操做符描述 操做符 优先级
转移符 \ 1
小括号和中括号 (…)、(?:…)、(?=…)、(?!…)、[…] 2
量词限定符 {m}、{m,n}、{m,}、?、*、+ 3
位置和序列 ^、$、元字符、通常字符 4
管道符 \ 5

经过操做符的优先级,咱们可以知道如何来读一个正则表达式。如下面这个正则表达式为例,咱们来介绍一下按照优先级进行分析的方法:

const reg = /ab?(c|de*)+|fg/;

// 第一步,根据优先级先考虑(c|de*)+,再根据优先级拆分获得c de*,即匹配c或者de*(注意,位置和序列的优先级高于管道符|,因此是c或de*而不是c或d和e*)
// 第二步,获得ab?,根据优先级拆分获得a和b?
// 第三步,获得fg,这个内容和第一步+第二步的结果为或的关系

最终,咱们获得的效果以下:

clipboard.png

经过这个图,你们就可以理解咱们的分析思路:先找括号,括号中的必定为一个总体(转移符只作转义,不分割正则,所以能够认为第一优先级实际上是括号),没有括号后再从左到右按照优先级进行分析。量词限定符则看作是正则的一个总体。

注:若是你们须要话相似的正则表达式流程图,可使用此网站

根据上面的优先级,咱们就可以避免在正则表达式的理解中出现归类错误的状况。

总结

本文经过介绍在正则表达式中容易被忽略的两个内容:回溯法操做优先级,让你们可以在进行正则的阅读和书写过程当中避免踩到相关的坑。

参考内容

  1. 《JavaScript正则表达式迷你书》——老姚 V1.1
  2. 《JavaScript权威指南》

个人博客即将搬运同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/dev...