掘金 后端 ( ) • 2024-04-18 18:13

什么是正则表达式?

正则表达式(Regular Expression,简称 Regex)是一种用于描述文本匹配模式的强大工具。它由一系列字符和特殊符号组成,可以简洁地描述复杂的文本模式。正则表达式常用于字符串搜索、替换、分词等文本处理任务。

正则表达式是每个开发人员必须掌握的一项技能。使用正则表达式可以在处理一些任务时非常方便快捷。

在线测试工具

  1. 将正则表达式以流程图式展示:https://regexper.com/
  2. 详细展示匹配的次数:https://regexr.com/, 可以通过短链接创建分享,【但是不能写后行断言】
  1. https://regex101.com/,【可以进行后行断言,网络不稳定】

记忆口诀

使用顺口溜记住些常用的元字符,及匹配规则,就可以满足日常使用。

  • w s d b d:常用的几个元字符,简称我说的都对

    • [w 字母数字下划线] [ s 空白字符] [b, 边界] [逗号 .] [d 0-9的数字]
  • 小卒[分组] 中活[或者] 大量[量词]

    • 小括弧():是分组,在象棋中,卒是小兵,
    • 中括弧[]:是选择其中的一个;在中国生活
    • 大括弧{}:是量词;
  • 关于小括弧分组的使用:

    • 引用分组使用:反斜扛 + 编号: \number
      • /\d{3}-(\d{5})-\1/;其中\1表示使用第一个分组的内容; 内容必须一致才能匹配
      • 案例/\d{3}-(\d{5})-\1/可以匹配 021-34422-34422
    • 问冒 不存分组(?:), 不保存分组;/\d{3}-(?:\d{5})-\1/就无法匹配 021-34422-34422
      • 案例:\d{3}-(?:\d{5})+(([a-z])+)-\1+可以匹配: 021-34422+ha-ha
  • 零宽断言:等号是肯定,叹号是否定,<符号是左移

语法

元字符、量词、修饰符、分组、贪婪与非贪婪

元字符

image.png

量词

元字符 含义 * 0个或多个 ? 0个或一个 + 一个或多个 {n} 出现n次 {n, m} 出现n到m次 {n,} 出现n次或多次

修饰符

  • i :ignoreCase 表示不区分大小写
  • g : global 表示全局匹配
  • m : multiline 表示多行匹配
  • s 表示允许.匹配换行符
  • u 使用unicode码的模式进行匹配
  • y 执行“粘性(sticky)”搜索,匹配从目标字符串的当前位置开始。

贪婪或惰性

在正则表达式中,使用量词修饰符进行匹配采用的是贪婪模式,就是尽可能多的匹配。

/.*/ 是单个非空白字符匹配任意次,即贪婪匹配

// 默认的贪婪模式匹配,会匹配到最后边的}符合,即把中间的years old,Bob is内容给替换掉了
var str = 'Anna is {age} years old,Bob is {age} years old too';
var expr = /{.*}/g;
console.log(str.replace(expr, '13'));
/*输出: Anna is 13 years old too*/

/.*?/ 是满足条件的情况只匹配一次,即懒惰匹配

// 懒惰匹配,匹配到}就立马结束。把两个{age}都匹配是因为g修饰符
var str = 'Anna is {age} years old,Bob is {age} years old too';
var expr = /{.*?}/g;
console.log(str.replace(expr, '13'));
/*输出: Anna is 13 years old,Bob is 13 years old too*/

分组

使用小括弧()是可以将正则进行分组。分组之后还可以对分组的内容进行再次复用;

默认分组是保存分组

let reg = /\d{3}-(\d{5})-\1/; // \1表示对第一个分组进行引用,匹配的值必须和第一个组内的值保持一致
let str = "021-34422-34422"; // 34422就是第一个分组的内容
reg.test(str); // true

可以通过 ?: 不保存分组

let reg = /\d{3}-(?:\d{5})\+(([a-z])+)-\1+/g; //第一个分组是5个数字,不保存第一个分组,只会匹配([a-z])+分组的内容
let str = '021-34422+ha-ha';
reg.test(str);

零宽断言

只断言不捕获,如同 ^ 代表开头, $ 代表结尾,\b代表单词边界一样,先行断言和后行断言也有类似的作用,它们只匹配某些位置,不占用字符,所以被称为 "零宽"

image.png

正则表达式的先行断言和后行断言一共有 4 种形式:

  • (?=pattern) 零宽正向先行断言 (zero-width positive lookahead assertion)
  • (?!pattern) 零宽负向先行断言 (zero-width negative lookahead assertion)
  • (?<=pattern) 零宽正向后行断言 (zero-width positive lookbehind assertion)
  • (?<!pattern) 零宽负向后行断言 (zero-width negative lookbehind assertion)

这里面的pattern是一个正则表达式。 < 称之为左移符号

先行断言(lookahead)

x(?=p) 正向先行断言

该 x 位置右侧能够匹配 p;

例如对"a regular expression",要想匹配 regular 中的 re,但不能匹配 expression 中的 re,可以用re(?=gular),该表达式限定 re 右边的位置,这个位置之后是 gular,但并不捕获 gular 这些字符。

image.png

x(?!p) 否定先行断言

该x位置右侧的 不能匹配 p

例如对"regex represents regular expression",要想匹配除 regex 和 regular 之外的 re,可以用re(?!g),该表达式限定了re右边的位置,这个位置后面不是字符g。

image.png

后行断言(lookbehind),添加 < 符号

之所以叫后行断言,是因为正则表达式引擎在匹配字符串和表达式时,是从前向后逐个扫描字符串中的字符,并判断是否与表达式符合,当在表达式中遇到该断言时,正则表达式引擎需要往字符串前端检测已扫描过的字符,相对于扫描方向是向后的。

(?<=p)x 正向后行断言

该x位置左侧的字符序列能够匹配 p

例如对regex represents regular expression,要想匹配单词内部的 re,但不匹配单词开头的 re,可以用 (?<=\w)re,单词内部的 re,在 re 前面应该是一个单词字符。

(?<!p)x 否定后行断言

该x左侧位置的字符序列不能匹配 p

例如对 "regex represents regular expression" 这个字符串,要想匹配单词开头的 re,可以用 (?<!\w)re。单词开头的re,在本例中,也就是指不在单词内部的re,即re前面不是单词字符。当然也可以用**\bre**来匹配。

image.png

正则的方法exec和test

exec的返回值是数组,test的返回值是布尔值。

exec的使用

let words= "happy learn RegExp's exec method";
let regx=/(\w{3})Ex(.{3})/g; //第一个括号可以用\w进行匹配第二个必须用.才能匹配到',否则为null
console.log(regx.exec(words));
// 返回数组
[
  "RegExp's",
  'Reg',
  "p's",
  index: 12,
  input: "happy learn RegExp's exec method",
  groups: undefined
]

exec()接受一个字符串参数,返回包含第一个匹配项信息的数组;如果没有匹配项的情况下返回null 返回的数组是Array实例,并且包含额外的属性: index 和 input, groups。

其中,

  • index 表示匹配项在字符串中的位置。(匹配项为RegExp's ,对应的位置是16);
  • input 表示应用正则表达式的字符串。(happy learn RegExp's exec method);

在数组中,

  • 第一项:表示与整个模式匹配的字符串 (代码中的 RegExp's 匹配 正则校验);
  • 其它项: 与表达式中用()包裹起来的,即捕获组匹配的字符串,第二项表示第一个捕获组的值,第三项表示第二个括号捕获的值(如果模式中没有捕获组regx = /\w{3}Ex.{3}/g;,则该数组只包含一项即RegExp's)

test的使用

let words = "happy learn RegExp's test method";
let regx = /\w{3}Ex.{3}/g;
let regOk = /\w{3}Ok.{3}/g;
console.log(regx.test(words)); // true
console.log(regOk.test(words)); // false

字符串的方法match和replace

match

match的使用和exec的作用一样,只不过是string身上的方法。

let words = "happy learn string's match method";
console.log(words.match(/(\w{3})ing(.{2})/));
// 返回结果
[
  "string's",
  'str',
  "'s",
  index: 12,
  input: "happy learn string's match method",
  groups: undefined
]

replace

提交掉字符串中符合正则的值

// 匹配值并进行替换
var re = /apples/gi;
var str = "Apples are round, and apples are juicy.";
var newstr = str.replace(re, "oranges");
console.log(newstr);
//输出内容 oranges are round, and oranges are juicy.
// 打印出通过()捕获的值
let words = "happy learn string's replace method";
words.replace(/(\w{3})ing(.{2})/g, function ($1, $2) {
  console.log($1, $2); // $1 $2分别为两个()捕获到的内容
});
// 将所有单词首字母大写
let words = "hello everybody, come on!";
console.log(
  words.replace(/\b\w*\b/g, (word) => {
    return word.substring(0, 1).toUpperCase() + word.substring(1);
  })
);
// Hello Everybody, Come On!
// 将句首字母大写
let words = "everybody, go! ";
console.log(words.replace(/^\b(\w)/g, (L) => L.toUpperCase()));

常用的正则示例

校验数字的表达式

let reg = /^[0-9]*$/; // 数字
reg = /^\d{n}$/; // n位数字
reg = /^\d{n,}$/; // 至少n位的数字
reg = /^\d{m,n}$/; // m至n位的数字
reg = /^(0|[1-9][0-9]*)$/; // 零和非零开头的数字
reg = /^([1-9][0-9]*)+(\.[0-9]{1,2})?$/; // 非零开头的最多带两位小数的数字
reg = /^(\-)?\d+(\.\d{1,2})$/; // 带1-2位小数的正数或负数
reg = /^(\-|\+)?\d+(\.\d+)?$/; // 正数、负数、或小数
reg = /^[0-9]+(\.[0-9]{2})?$/; // 有两位小数的正实数
reg = /^[0-9]+(\.[0-9]{1,3})?$/; // 有1~3位小数的正实数
reg = /^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$/ 或 reg= /^\+?[1-9][0-9]*$/; // 非零的正整数
reg = /^\-[1-9][]0-9"*$/ 或 reg = /^-[1-9]\d*$/; // 非零的负整数
reg = /^\d+$/ 或 reg = /^[1-9]\d*|0$/; // 非负整数
reg = /^-[1-9]\d*|0$/ 或 reg = /^((-\d+)|(0+))$/; // 非正整数
reg = /^\d+(\.\d+)?$/ 或 reg = /^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$/; // 非负浮点数
reg = /^((-\d+(\.\d+)?)|(0+(\.0+)?))$/ 或 reg = /^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$/; // 非正浮点数
reg = /^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$/ 或 reg = /^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$/; // 正浮点数
reg = /^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$/ 或 reg = /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/; // 负浮点数
reg = /^(-?\d+)(\.\d+)?$/ 或 reg = /^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$/; // 浮点数

验证是有效数字

规则分析:

  • 开头可能是-或者+,也可能不出现; [-+]? 或者(-|+)?
  • 第一位是0-9都可以,0不能重复出现在首部; (\d|([1-9]\d+))
  • 小数部分可能存在,也可能不存在;但是如果有小数点,则点后边必须有数字;(.\d+)?
let reg = /^[-+]?(\d|([1-9]\d+))(\.\d+)?$/;
let num = '0.569';
reg.test(num); // true
num = '+1369';
reg.test(num); // true
num = '-2.569';
reg.test(num); // true

num = '-002.569';
reg.test(num);  // false

校验字符的表达式

let reg = /^[\u4e00-\u9fa5]{0,}$/; // 汉字
reg = /^[A-Za-z0-9]+$/ 或 reg = /^[A-Za-z0-9]{4,40}$/; // 英文和数字
reg = /^.{3,20}$/; // 长度为3-20的所有字符
reg = /^[A-Za-z]+$/; // 由26个英文字母组成的字符串
reg = /^[A-Z]+$/; // 由26个大写英文字母组成的字符串
reg = /^[a-z]+$/; // 由26个小写英文字母组成的字符串
reg = /^[A-Za-z0-9]+$/; // 由数字和26个英文字母组成的字符串
reg = /^\w+$/ 或 reg = /^\w{3,20}$/; // 由数字、26个英文字母或者下划线组成的字符串
reg = /^[\u4E00-\u9FA5A-Za-z0-9_]+$/; // 中文、英文、数字包括下划线
reg = /^[\u4E00-\u9FA5A-Za-z0-9]+$/ 或 reg = /^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$/; // 中文、英文、数字但不包括下划线等符号
reg = /[^%&',;=?$\x22]+/; // 可以输入含有^%&',;=?$\"等字符
reg = /[^~\x22]+/; // 禁止输入含有~的字符

特殊需求表达式

let reg = /[1-9][0-9]{4,}/; // 腾讯QQ号(腾讯QQ号从10000开始)
reg = /[1-9]\d{5}(?!\d)/; // 中国邮政编码
reg = /((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/; // IPv4地址
reg = /^\d{4}-\d{1,2}-\d{1,2}/; // 日期格式
reg = /^(0?[1-9]|1[0-2])$/; // 一年的12个月(01~09和1~12)
reg = /^((0?[1-9])|((1|2)[0-9])|30|31)$/; // 一个月的31天(01~09和1~31)
reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; // 身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X
reg = /\d{3}-\d{8}|\d{4}-\d{7}/; // 国内电话号码(0511-4405222、021-87888822)
reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/; // Email地址
reg = /[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/; // 域名
reg = /[a-zA-z]+://[^\s]*/ 或 reg = /^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$/; // InternetURL
reg = /^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/; // 手机号码
reg = /((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)/; // 电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号)
reg = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$/; // 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线)
reg = /^[a-zA-Z]\w{5,17}$/; // 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线)
// ============强密码==============
reg = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{8,10}$/; // 必须包含大小写字母和数字的组合,不能使用特殊字符,长度在 8-10 之间
reg = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$/; // 必须包含大小写字母和数字的组合,可以使用特殊字符,长度在8-10之间
reg = /((?=.*[a-z])(?=.*\d)|(?=[a-z])(?=.*[<>;':",.$#@!~%^&*])|(?=.*\d)(?=.*[<>;':",.$#@!~%^&*]))[a-z\d<>;':",.$#@!~%^&*]{8,16}/; // 可输入特殊字符、字母、数字,必须包含其中两种,长度在8-16之间