首页 > 编程语言 >【正则】174-《JavaScript 正则迷你书》知识点小抄本(下)

【正则】174-《JavaScript 正则迷你书》知识点小抄本(下)

时间:2022-11-28 14:02:53浏览次数:38  
标签:知识点 匹配 16 03 JavaScript 正则 2019 let match

四、正则表达式回溯法原理

概念理解起来比较容易。
比如用 ​​​/ab{1,3}c/​​ 去匹配下面两个字符串。

  • 当匹配 ​​abbbc​​,按顺序匹配,到了第 3 个 ​​b​​ 后,直接匹配 ​​c​​,这样就没有回溯。
  • 当匹配 ​​abbc​​,按顺序匹配,到了第 2 个 ​​b​​ 后,由于规则是 ​​b{1,3}​​ ,则会继续往下匹配,然后发现下一位是 ​​c​​,于是回退到前一个位置,重新匹配,这就是回溯。

另外像 ​​/".*"/​​​ 来匹配 ​​"abc"de​​​ 的话,就会有三个回溯情况,为了减少不必要的回溯,我们可以把正则修改为 ​​/"[^"]*"/​​。

介绍

回溯法,也称试探法,本质上是深度优先探索算法,基本思路是:匹配过程中后退到之前某一步重新探索的过程。

1. 常见的回溯形式

  • 贪婪量词

多个贪婪量词挨着存在,并相互冲突时,会看匹配顺序,深度优先搜索:

  1. ​"12345".match(/(\d{1,3})(\d{1,3})/);​
  2. ​//  ["12345", "123", "45", index: 0, input: "12345"]​
  • 惰性量词

有时候会因为回溯,导致实际惰性量词匹配到的不是最少的数量:

  1. ​"12345".match(/(\d{1,3}?)(\d{1,3})/);​
  2. ​// 没有回溯的情况 ["1234", "1", "234", index: 0, input: "12345"]​

  3. ​"12345".match(/^\d{1,3}?\d{1,3}$/);​
  4. ​// 有回溯的情况 ["12345", index: 0, input: "12345"]​
  • 分支结构

分支机构,如果一个分支整体不匹配,会继续尝试剩下分支,也可以看成一种回溯。

  1. ​"candy".match(/can|candy/); // ["can", index: 0, input: "candy"]​

  2. ​"candy".match(/^(?:can|candy)$/); // ["candy", index: 0, input: "candy"]​

2. 本章小结

简单总结:一个个尝试,直到,要么后退某一步整体匹配成功,要么最后试完发现整体不匹配。

  • 贪婪量词:买衣服砍价,价格高了,便宜点,再便宜点。
  • 懒惰量词:卖衣服加价,价格低了,多给点,再多给点。
  • 分支结构:货比三家,一家不行换一家,不行再换。

五、正则表达式的拆分

拆分正则代码块,是理解正则的关键。

在 JavaScrip 正则表达式有以下结构:

  • 字面量: 匹配一个具体字符,如 ​​a​​​ 匹配字符 ​​a​​。
  • 字符组: 匹配一个有多种可能性的字符,如 ​​[0-9]​​ 匹配任意一个数字。
  • 量词: 匹配一个连续出现的字符,如 ​​a{1,3}​​​ 匹配连续最多出现 3 次的 ​​a​​字符。
  • 锚: 匹配一个位置,如 ​​^​​ 匹配字符串的开头。
  • 分组: 匹配一个整体,如 ​​(ab)​​​ 匹配 ​​ab​​ 两个字符连续出现。
  • 分支: 匹配一个或多个表达式,如 ​​ab|bc​​​ 匹配 ​​ab​​ 或 ​​bc​​ 字符。

另外还有以下操作符:

优先级

操作符描述

操作符

1

转义符

​\​

2

括号和方括号

​(...)​​/ ​​(?:...)​​/ ​​(?=...)​​/ ​​(?!...)​​/ ​​[...]​

3

量词限定符

​{m}​​/ ​​{m,n}​​/ ​​{m,}​​/ ​​?​​/ ​​*​​/ ​​+​

4

位置和序列

​^​​/ ​​$​​/ ​​\元字符​​/ ​​一般字符​

5

管道符

`

Tips:优先级从上到下,由高到低。

1. 注意要点

  • 匹配字符串整体

不能写成 ​​/^abc|bcd$/​​​ ,而是要写成 ​​/^(abc|bcd)$/​​。

  • 量词连缀问题

需要匹配:每个字符是 ​​a​​​/ ​​b​​​/ ​​c​​ 中其中一个,并且字符串长度是 3 的倍数:

不能写成 ​​/^[abc]{3}+$/​​​ ,而是要写成 ​​/([abc]{3})+/​​。

  • 元字符转义问题

元字符就是正则中的特殊字符,当匹配元字符就需要转义,如:

​^​​​、 ​​$​​​、 ​​.​​​、 ​​*​​​、 ​​+​​​、 ​​?​​​、 ​​|​​​、 ​​\​​​、 ​​/​​​、 ​​(​​​、 ​​)​​​、 ​​[​​​、 ​​]​​​、 ​​{​​​、 ​​}​​​、 ​​=​​​、 ​​!​​​、 ​​:​​​、 ​​-​​ 。

  1. ​// "[abc]" => /\[abc\]/ 或者 /\[abc]/ ​
  2. ​// "{1,3}" => /\{1\}/ 或者 /\{1}/ 因为不构成字符组​

2. 案例分析

  • 身份证号码
  1. ​/^(\d{15}|\d{17})[\dxX]$/.test("390999199999999999");// true​
  • IPV4地址

需要好好分析:

  1. ​let r = /^((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])$/​

六、正则表达式的构建

正则的构建需要考虑以下几点的平衡:

  • 匹配预期的字符串
  • 不匹配非预期的字符串
  • 可读性和可维护性
  • 效率

我们还需要考虑这么几个问题:

  • 是否需要使用正则

如能使用其他 API 简单快速解决问题就不需要使用正则:

  1. ​"2019-03-16".match(/^(\d{4})-(\d{2})-(\d{2})/); // 间接获取 ["2019", "03", "16"]​
  2. ​"2019-03-16".split("-"); //  ["2019", "03", "16"]​

  3. ​"?id=leo".search(/\?/); // 0​
  4. ​"?id=leo".indexOf("?"); // 0​

  5. ​"JavaScript".match(/.{4}(.+)/)[1]; // "Script"​
  6. ​"JavaScript".substring(4); // "Script"​
  • 是否需要使用复杂正则

​/(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/​

将这个正则拆分成多个小块,如下:

  1. ​var regex1 = /^[0-9A-Za-z]{6,12}$/;​
  2. ​var regex2 = /^[0-9]{6,12}$/;​
  3. ​var regex3 = /^[A-Z]{6,12}$/;​
  4. ​var regex4 = /^[a-z]{6,12}$/;​
  5. ​function checkPassword (string) {​
  6. ​ if (!regex1.test(string)) return false;​
  7. ​ if (regex2.test(string)) return false;​
  8. ​ if (regex3.test(string)) return false;​
  9. ​ if (regex4.test(string)) return false;​
  10. ​ return true;​
  11. ​}​

1. 准确性

即需要匹配到预期目标,且不匹配非预期的目标。

  • 匹配固定电话

如需要匹配下面固定电话号码,可以分别写出对应正则:

  1. ​055188888888 => /^0\d{2,3}[1-9]\d{6,7}$/​
  2. ​0551-88888888 => /^0\d{2,3}-[1-9]\d{6,7}$/​
  3. ​(0551)88888888 => /^0\d{2,3}-[1-9]\d{6,7}$/​

然后合并:

  1. ​let r = /^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}$/​

然后提取公共部分:

  1. ​let r = /^(0\d{2,3}|0\d{2,3}-|\(0\d{2,3}\))[1-9]\d{6,7}$/​

再优化:

  1. ​let r = /^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/​
  • 匹配浮点数

先确定,符号部分( ​​[+-]​​​)、整数部分( ​​\d+​​​)和小数部分( ​​\.\d+​​)。

  1. ​1.23、+1.23、-1.23 => /^[+-]?\d+\.\d+$/​
  2. ​10、+10、-10 => /^[+-]?\d+$/​
  3. ​.2、+.2、-.2 => /^[+-]?\.\d+$/​

整理后:

  1. ​let r = /^[+-]?(\d+\.\d+|\d+|\.\d+)$/;​

  2. ​// 考虑不匹配 +.2 或 -.2​
  3. ​let r = /^([+-])?(\d+\.\d+|\d+|\.\d+)$/;​

  4. ​// 考虑不匹配 012 这类 0 开头的整数​
  5. ​let r = /^[+-]?(\d+)?(\.)?\d+$/;​

2. 效率

正则表达式运行过程:

  1. 编译
  2. 设定起始位置
  3. 尝试匹配
  4. 若匹配失败则返回前一步重新匹配
  5. 返回匹配成功失败的结果

我们常常优化对 ​​3和4​​ 步进行优化:

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

如 ​​/"[^"]*"/​​​ 代替 ​​/".*?"/​​。

  • 使用非捕获型分组

当不需要使用分组引用和反向引用时,此时可以使用非捕获分组。

如 ​​/^[-]?(?:\d\.\d+|\d+|\.\d+)$/​​​ 代替 ​​/^[-]?(\d\.\d+|\d+|\.\d+)$/​​。

  • 独立出确定字符

加快判断是否匹配失败,进而加快移位的速度。

如 ​​/aa*/​​​ 代替 ​​/a+/​​。

  • 提取分支公共部分

减少匹配过程中可消除的重复。

如 ​​/^(?:abc|def)/​​​ 代替 ​​/^abc|^def/​​。

  • 减少分支的数量,缩小它们的范围

如 ​​/rea?d/​​​ 代替 ​​/red|read/​​。

七、正则表达式编程

这里要掌握正则表达式怎么用,通常会有这么四个操作:

  • 验证
  • 切分
  • 提取
  • 替换

1. 四种操作

  • 验证

匹配本质上是查找,我们可以借助相关API操作:

  1. ​// 检查字符串是否包含数字​
  2. ​let r = /\d/, s = "abc123";​
  3. ​!!s.search(r); // true​
  4. ​r.test(s); // true​
  5. ​!!s.match(r); // true​
  6. ​!!r.exec(s); // true​
  • 切分
  1. ​"leo,pingan".split(/,/); // ["leo", "pingan"]​

  2. ​let r = /\D/, s = "2019-03-16";​
  3. ​s.split(r); // ["2019", "03", "16"]​
  4. ​s.split(r); // ["2019", "03", "16"]​
  5. ​s.split(r); // ["2019", "03", "16"]​
  • 提取
  1. ​// 提取日期年月日​
  2. ​let r = /^(\d{4})\D(\d{2})\D(\d{2})$/;​
  3. ​let s = "2019-03-16";​

  4. ​s.match(r); // ["2019-03-16", "2019", "03", "16", index: 0, input: "2019-03-16"]​
  5. ​r.exec(s); // ["2019-03-16", "2019", "03", "16", index: 0, input: "2019-03-16"]​
  6. ​r.test(s); // RegExp.$1 => "2019" RegExp.$2 => "03" RegExp.$3 => "16"​
  7. ​s.search(r);// RegExp.$1 => "2019" RegExp.$2 => "03" RegExp.$3 => "16"​
  • 替换
  1. ​// yyyy-mm-dd 替换成 yyyy/mm/dd​
  2. ​"2019-03-16".replace(/-/g, "/");​

2. 相关API注意

  • ​search​​​ 和 ​​match​​ 参数问题

这两个方法会把字符串转换成正则,所以要加转义

  1. ​let s = "2019.03.16";​
  2. ​s.search('.'); // 0​
  3. ​s.search('\\.'); // 4​
  4. ​s.search(/\./); // 4​
  5. ​s.match('.'); // ["2", index: 0, input: "2019.03.16"]​
  6. ​s.match('\\.'); // [".", index: 4, input: "2019.03.16"]​
  7. ​s.match(/\./); // [".", index: 4, input: "2019.03.16"]​

  8. ​// 其他不用转义​
  9. ​s.split('.');​
  10. ​s.replace('.', '/');​
  • ​match​​ 返回结果的格式问题

​match​​​ 参数有 ​​g​​​ 会返回所有匹配的内容,没有 ​​g​​ 则返回标准匹配格式:

  1. ​let s = "2019.03.16";​
  2. ​s.match(/\b(\d+)\b/); // ["2019", "2019", index: 0, input: "2019.03.16"]​
  3. ​s.match(/\b(\d+)\b/g); // ["2019", "03", "16"]​
  • ​test​​​ 整体匹配时需要使用 ​​^​​ 和 ​​$​
  1. ​/123/.test("a123b"); // true​
  2. ​/^123$/.test("a123b"); // false​
  3. ​/^123$/.test("123"); // true​
  • ​split​​ 的注意点

​split​​ 第二个参数是 结果数组的最大长度

  1. ​"leo,pingan,pingan8787".split(/,/, 2); // ["leo", "pingan"]​

使用正则分组,会包含分隔符:

  1. ​"leo,pingan,pingan8787".split(/(,)/); // ["leo", ",", "pingan", ",", "pingan8787"]​
  • 修饰符

修饰符

描述

​g​

全局匹配,即找到所有匹配的,单词是 ​​global​​。

​i​

忽略字母大小写,单词是 ​​ingoreCase​​。

​m​

多行匹配,只影响 ​​^​​ 和 ​​$​​,二者变成行的概念,即行开头和行结尾。单词是 ​​multiline​​。

文章到这结束,感谢阅读,也感谢老姚大佬的这本书

【正则】174-《JavaScript 正则迷你书》知识点小抄本(下)_字符串


标签:知识点,匹配,16,03,JavaScript,正则,2019,let,match
From: https://blog.51cto.com/u_11887782/5891092

相关文章

  • 【JS】165-JavaScript设计模式——工厂模式
    二、工厂模式(FactoryPattern)1.概念介绍工厂模式的目的在于创建对象,实现下列目标:可重复执行,来创建相似对象;当编译时位置具体类型(类)时,为调用者提供一种创建对象的接口;通过......
  • 【JS】164-JavaScript设计模式——单体模式
    一、单体模式(SingletonPattern)1.概念介绍单体模式(SingletonPattern)的思想在于保证一个特定类仅有一个实例,即不管使用这个类创建多少个新对象,都会得到与第一次创建的对......
  • 【正则】223-JS常用正则表达式备忘录
    ​翻译自RegexCheatSheet(https://dev.to/emmawedekind/regex-cheat-sheet-2j2a)翻译:前端小智整理编辑:SegmentFault正则表达式或“regex”用于匹配字符串的各个部分,下面是作......
  • 拓端tecdat|【视频】Lasso回归、岭回归等正则化回归数学原理及R语言实例
    在本视频中,我们将介绍Lasso套索回归、岭回归等​​正则化​​的回归方法的数学原理以及R软件实例。视频:Lasso回归、岭回归正则化回归数学原理及R软件实例Lasso回归、岭回归......
  • 12个有用的JavaScript数组技巧
    数组是Javascript最常见的概念之一,它为我们提供了处理数据的许多可能性,熟悉数组的一些常用操作是很有必要的。1、数组去重1、from()叠加newSet()方法字符串或数值型数......
  • 前端知识点概览
    0、底层EventLoop事件循环:就是一个执行消息队列的机制宏任务微任务为了解决这种情况,将任务分为了同步任务和异步任务;而异步任务被分为两种,一种宏任务(MacroTask),一种......
  • 运行 JavaScript 代码片段
    原文链接​​RunsnippetsofJavaScript​​--作者​​KayceBasques​​&​​SofiaEmelianova​​如果你发现自己反复使用​​Console​​来运行同一份代码,那么......
  • Java中使用正则表达式
    1、使用 java.util.regex.Pattern类的 compole(表达式)方法把正则表达式变成一个对象。//表达式对象:1个数字和1个字母连续Patternpattern=P......
  • 拓端tecdat|R语言代写线性判别分析(LDA),二次判别分析(QDA)和正则判别分析(RDA)
    判别分析包括可用于分类和降维的方法。线性判别分析(LDA)特别受欢迎,因为它既是分类器又是降维技术。二次判别分析(QDA)是LDA的变体,允许数据的非线性分离。最后,正则化判别分析(RDA......
  • 2022UNCTFcrypto知识点
    2022UNCTFcrypto知识点dddd110/01/0101/0/1101/0000100/0100/11110/111/110010/0/1111/10000/111/110010/1000/110/111/0/110010/00/00000/101/111/1/0000010一般出现0......