正则从入门到放弃

前言

本人参考了 前端胖头鱼 的文章,或者说搬运的大部分内容,但也自己做了部分补充和修改。
原文地址 - 作者 前端胖头鱼
原文地址 - 作者 老姚

请心理默念三遍

  • 正则表达式是匹配模式,要么匹配字符,要么匹配位置
  • 正则表达式是匹配模式,要么匹配字符,要么匹配位置
  • 正则表达式是匹配模式,要么匹配字符,要么匹配位置

位置匹配

此部分内容都是在确定或者寻找位置,常用位置 ^$\b\B?=p(?!p)(?<=p)(?<!p)

脱字符 ^ ,匹配行的开头

在 hello 的开头塞一个 😍
const str = "hello";
str.replace(/^/, "😍");
// 输出: 😍hello
匹配 he 开头的字符串
const re = /^he/;
re.test("he"); // 输出: true
re.test("hello"); // 输出: true
re.test(" he"); // 输出: false

美元符 $ ,匹配行的结尾

在 hello 的结尾塞一个 😍
const str = "hello";
str.replace(/$/, "😍");
// 输出: hello😍
匹配 lo 结尾的字符串
const re = /lo$/;
re.test("lo"); // 输出: true
re.test("hello"); // 输出: true
re.test("lo "); // 输出: false

单词边界 \b ,匹配单词边界

匹配规则

  1. \w 和\W 之间的位置
  2. ^与\w 之间的位置
  3. \w 与 $ 之间的位置

\w 匹配字母、数字、下划线。等价于[A-Za-z0-9_]

\W 匹配非单词字符。等价于[^A-Za-z0-9_]

"[xxx]_xxx.mp4".replace(/\b/g, "🆚");
// 输出: 🆚xxx_xxx🆚.🆚mp4🆚
  • 第一个插入为 ^与\w 之间的位置
  • 第二个与第三个插入为 \w 和\W 之间的位置
  • 第四个插入为 \w 与 $ 之间的位置

非单词边界 \B

匹配规则

  1. \w\w 之间的位置
  2. \W\W 之间的位置
  3. ^\W 之间的位置
  4. \W$ 之间的位置
"@!xxx_xxx.mp4!@".replace(/\B/g, "🆚");
// 输出: 🆚@🆚!x🆚x🆚x🆚_🆚x🆚x🆚x.m🆚p🆚4!🆚@🆚

正向肯定断言 (?=p)

匹配 p 前面的位置

// 在 abc 与 123 中间加下划线 '_'
"abc123".replace(/(?=123)/g, "_");
// 输出: abc_123

// -------------------------------

// 在 === 号前加 '!'
"123==='123'".replace(/(?=\={3})+/g, "!");
// 输出: 123!==='123'

正向否定断言 (?!p)

匹配 (?=p)匹配到的位置之外的位置都是属于(?!p)

// 在除字符串`开头`之外的位置加下划线
"abc123".replace(/(?!^)/g, "_");
// 输出: a_b_c_1_2_3_

// 在除字符串`结尾`之外的位置加下划线
"abc123".replace(/(?!$)/g, "_");
// 输出: _a_b_c_1_2_3

// 在除字符串`开头` 与 `结尾` 之外的位置加下划线
"abc123".replace(/(?!^)(?!$)/g, "_");
// 输出: a_b_c_1_2_3

(反向|负向)肯定断言 (?<=p)

匹配 p 后面的位置

// 在 abc 与 123 中间加下划线 '_'
"abc123".replace(/(?<=abc)/g, "_");
// 输出: abc_123

(反向|负向)否定断言 (?<=p)

匹配 (?<!p)匹配到的位置之外的位置都是属于(?<!p)

// 在除abc与123之外的位置加下划线
"abc123".replace(/(?<!abc)/g, "_");
// 输出: _a_b_c1_2_3_

题目练习

题目 1:数字的千分位分割法

将 123456789 转化为 123,456,789

// 匹配字符串结尾三个数字前的位置
str.replace(/(?=\d{3}$)/g, ",");
// 输出: 123456,789

// 已三个数字为一组,每组之间用逗号隔开
str.replace(/(?=(\d{3})+$)/g, ",");
// 输出: ,123,456,789

// 去除开始的逗号
str.replace(/(?=(\d{3})+$)(?!^)/g, ",");
// 输出: 123,456,789

题目 2:手机号 3-4-4 分割

将手机号 18379836654 转化为 183-7983-6654

str.replace(/(?=(\d{4})+$)/g, "-");
// 输出: 183-7983-6654

题目 3:验证密码的合法性

密码长度是 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符

// 1. 密码由数字、小写字母和大写字母组成,并且长度在 6-12 位之间
const re = /^[a-zA-Z\d]{6,12}$/;
re.test("123456"); // true
re.test("abcdefg"); // true
re.test("ABCDEFG"); // true
re.test("123"); // false
re.test("1234567890123"); // false

// 2. 数字出现的位置紧接着是小写字母;
const re = /(?=.*\d[a-zA-z])/;
re.test("1a"); // true
re.test("1A"); // true
re.test("11"); // false

// 3.小写字母出现的位置紧接着是大写字母或数字;
const re = /(?=.*[a-z][A-Z\d])/;
re.test("a1"); // true
re.test("aA"); // true
re.test("aa"); // false

// 4. 大写字母出现的位置紧接着是小写字母或数字;
const re = /(?=.*[A-Z][a-z\d])/;
re.test("A1"); // true
re.test("Aa"); // true
re.test("AA"); // false

// 5. 将1-4的正则表达式组合成一个大的正则表达式,并且验证密码的合法性
const re =
  /((?=.*\d[a-zA-z])|(?=.*[a-z][A-Z\d])|(?=.*[A-Z][a-z\d]))^[a-zA-Z\d]{6,12}$/;
re.test("1aA"); // false  不满足条件1
re.test("123456"); // false  不满足条件2
re.test("abcdef"); // false  不满足条件3
re.test("ABCDEF"); // false  不满足条件4
re.test("12345a"); // true
re.test("12345A"); // true
re.test("a12345"); // true
re.test("aA2345"); // true
re.test("A12345"); // true
re.test("Aa2345"); // true

字符串匹配

此部分内容都是在寻找字符串来匹配

横向查找

一个正则可匹配的字符串的长度不是固定的,可以是多种情况

常用量词

字符描述字符描述
\*匹配 0 个或多个字符?匹配 0 个或 1 个字符
+匹配 1 个或多个字符{m,n}匹配 m~n 个字符
  • \* 匹配 0 个或多个字符
    /^\d*$/.test(""); // true
    /^\d*$/.test("1"); // true
    
  • + 匹配 1 个或多个字符
    /^\d+$/.test(""); // false
    /^\d+$/.test("1"); // true
    
  • ? 匹配 0 个或 1 个字符
    /^\d?$/.test(""); // true
    /^\d?$/.test("1"); // true
    
  • {m,n} 匹配 m~n 个字符
    // {n} 匹配 3个1
    /^1{3}/.test("111"); // true
    // {m,n} 匹配 1~2 个字符
    /^\d{1,2}$/.test(""); // false
    /^\d{1,2}$/.test("1"); // true
    /^\d{1,2}$/.test("12"); // true
    /^\d{1,2}$/.test("123"); // false
    // {m,} 匹配 1到多个字符 等同于 `/^\d+$/`
    /^\d{1,}$/.test("1"); // true
    

纵向查找

一个正则匹配的字符串,具体到某一位字符时,可以不是某个确定的字符串,可以有多种可能,实现方式是字符组( 其实多选分支|也可以实现 )

字符组 []

  • 范围表示法

    [123456abcdefABCDEF] => [1-6a-fA-F]

    // 匹配 a,b,c,d 中的任意一个
    /^[abcd]$/.test("a") // true
    
    // 匹配 a,b,c,d 中的任意一个
    /^[a-d]$/.test("a") // true
    
    // 匹配 0 - 9 中的任意一个
    /^[0-9]$/.test("1") // true
    
  • 排除字符组

    // 不匹配 a,b,c,d 中的任意一个
    /^[^e]$/.test("a"); // false
    

贪婪匹配

正则本身是贪婪的,会尽可能的多匹配符合模式的字符

"1234".replace(/\d+/, "*"); // "****"

非贪婪匹配

在量词后面加上 ? 可以实现非贪婪匹配

"1234".replace(/\d+?/, "*"); // "*234"

多选分支

一个模式可以实现横向和纵向的模糊匹配,而多选分支可以支持多个子模式任选其一,形式是(p1|p2|p3)

let regex = /good|nice/g;
let string = "good idea, nice try.";

string.match(regex); // [ 'good', 'nice' ]

注意,用/good|goodbye/去匹配goodbye 匹配到的是 good 因为分支结构是惰性的,前面的匹配上了,后面的就不再尝试了

案例分析

  • 匹配 id

    // 贪婪模式
    '<div id="container" class="main"></div>'.match(/id=".*"/gi);
    // 输出 [ 'id="container" class="main"' ]
    
    // 非贪婪模式
    '<div id="container" class="main"></div>'.match(/id=".*?"/gi);
    // 输出 [ 'id="container"' ]
    
  • 匹配 16 进制的颜色值

    // 要求匹配如下颜色
    /*
     * #ffbbad #Fc01DF #FFF #ffE
     */
    let regex = /#([a-fA-F\d]{6}|[a-fA-F\d]{3})/g;
    let string = "#ffbbad #Fc01DF #FFF #ffE";
    string.match(regex);
    // ["#ffbbad", "#Fc01DF", "#FFF", "#ffE"]
    
    • 校验小数与整数

      • 校验正整数与负整数

        .字符串开头可能存在+或者-,且-0为非法,若是多个字符,则第一个字符必须是[1-9],若是一个字符必须是[0-9]

        const re = /^[-+]?([1-9]\d{0,}|0)$/;
        re.test("1"); // true
        re.test("12"); // true
        re.test("+12"); // true
        re.test("-12"); // true
        re.test("+0"); // true
        re.test("-0"); // true
        re.test("01"); // false
        re.test("+01"); // false
        re.test("-01"); // false
        
      • 增加小数校验

        在整数正则后增加小数判断,.与数字必须同时存在且只能存在一个..与数字可能存在可能不存在

        const re = /^(-?[1-9]{1}\d{0,}|0)(\.\d+)?$/;
        re1.test("1.2"); // true
        re1.test("1.2.3.4"); // false
        
    • 匹配 24 小时制时间

      // 第一位:可以是0、1、2
      // 第二位:当第一位位0或者1的时候,可以是0到9、第一位是2的时候,只可以是0到3
      // 第三位:固定是冒号:
      // 第四位:可以是0到5
      // 第五位:0到9
      const re = /([0-1]\d|2[0-3]):[0-5]\d/;
      console.log(re.test("00:00")); // true
      console.log(re.test("10:00")); // true
      console.log(re.test("23:59")); // true
      console.log(re.test("24:00")); // false
      
      // 衍生题,可以小时首位为0可省略
      const re = /(0?\d|[0-1]\d|2[0-3]):[0-5]\d/;
      console.log(re.test("7:00")); // true
      console.log(re.test("07:00")); // true
      

括号的作用

括号的作用是提供了分组(括号内的正则是一个整体,即提供子表达式),便于我们引用它

分组

让量词作用于一个整体

const reg = /(ab)+/g;
const string = "ababa abbb ababab";

string.match(reg); // ["abab", "ab", "ababab"]

分支结构

分支结构有点像编程里面或的概念||

/*
匹配 
I love JavaScript
I love Regular Expression
*/

const reg = /I love (JavaScript|Regular Expression)/;

reg.test("I love JavaScript"); // true
reg.test("I love Regular Expression"); // true

分组的引用

将分组的内容引用到另一个分组中

2021-03-31 转换为 2021.03.31
  • 第一种方法

    const reg = /(\d{4})-(\d{2})-(\d{2})/;
    const string = "2021-03-31";
    string.replace(reg, "$1.$2.$3"); // 2021.03.31
    
  • 第二种方法

    const reg = /(\d{4})-(\d{2})-(\d{2})/;
    const string = "2021-03-31";
    string.replace(reg, () => {
      return RegExp.$1 + "." + RegExp.$2 + "." + RegExp.$3;
    }); // 2021.03.31
    
  • 第三种方法

    const reg = /(\d{4})-(\d{2})-(\d{2})/;
    const string = "2021-03-31";
    string.replace(reg, ($0, $1, $2, $3) => {
      return $1 + "." + $2 + "." + $3;
    }); // 2021.03.31
    

反向引用

除了通过 js 引用分组的内容,也可以通过正则来引用分组内容

  • 注意
    • 引用不存在的分组会怎样?
      • 即匹配的就是\1 \2 本身
    • 分组后面有量词会怎样
      • 分组后面如果有量词,分组最终(注意是分组,不是说整体)捕获的数据是最后一次的匹配
      /(\d)+ \1/.test('12345 1') // false
      /(\d)+ \1/.test('12345 5') // true
      
  • 匹配 AAAA

    // 匹配 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999
    // 1.获取匹配的第一个分组
    // 2.将匹配到的第一个分组引用并且增加3次匹配
    /(\d)\1{3}/.test("1111");
    
  • 匹配 AABB

    // 匹配 1122, 2233
    // 1.匹配第一个分组,然后直接引用
    // 2.第二个分组中不应该存在 分组1中的数字。 过滤掉 AAAA
    const re = /(\d)\1((?!\1)\d)\2/;
    re.test("1122"); // true
    re.test("1111"); // false
    
  • 多格式时间匹配

    // 2021-03-31  2021/03/31 2021.03.31
    const re = /\d{4}([-\/-])\d{2}\1\d{2}/;
    re.test("2021-03-31"); // true
    re.test("2021/03/31"); // true
    re.test("2021.03.31"); // true
    re.test("2021.03-31"); // false
    

非捕获性括号

匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 字符 (|) 来组合一个模式的各个部分是很有用。

// 示例
const re1 = /industr(?:y|ies)/;
re1.test("industry"); // true
re1.test("industries"); // true

const re2 = /industry|industries/;
re2.test("industry"); // true
re2.test("industries"); // true
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赵忠洋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值