持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
正则表达式专题
邂逅正则表达式
正则表达式基础
/*
* @Author: 毛毛
* @Date: 2022-03-06 12:18:14
* @Last Modified by: 毛毛
* @Last Modified time: 2022-03-07 09:33:48
*/
/* 正则表达式 ->
1. 只能处理字符串
2. 是一个规则:可以验证字符串是否符合某个规则(test)
也可以吧字符串中符合规则的内容捕获到(exec / match...)
*/
let str = "good good study, day day up!";
// 创建一个正则
/**
* \d => 0-9的数字
* + => 出现一次或任意多次
*/
let reg = /\d+/;
console.log(reg.test(str))
str = "2019-08-12";
const arr = reg.exec(str);
console.log(arr) // ['2019', index: 0, input: '2019-08-12', groups: undefined]
/*
正则创建方式:
1. /写正则规则/
2. new RegExp(元字符字符串, 修饰符字符串)
正则的组成:
@1. 元字符: 定义当前正则的规则
常用的原字符:
1): 量词元字符:设置出现的次数 6个
a) * 表示零到多次
b) + 一到多次
c) ? 零次 或者 一次
d) {n} 出现 n次
e) {n,} 出现 n 及 n+ 次
f) {n,m} 出现 n 到 m 次
2) 特殊元字符:单个或者组合在一起代表特殊的含义 20个
单个元字符
a) \ 转义字符 (普通 -> 特殊 -> 普通)
b) . 除 \n(换行符) 以外的任意字符
c) ^ 以 哪一个元字符作为开始
d) $ 以 那个元字符作为结束
// --------------------------------
组合元字符
e) \n 换行符 \1 后面出现和前面匹配的内容一样 且出现多次
f) \d 0-9之间的数字 , \D 非 0-9之间的数字
g) \w 数字 字母 下划线 中的任意一个字符 , \W 非 数字 字母 下划线 中的任意一个字符
h) \s 一个空白字符(空格 制表符tab 换页符等) \t 一个制表符 (tab: 四个空格)
i) \b 字符边界 \B 取反
// ----------------------------------------
j) x|y x或者y中的一个字符
k) [abc] a或b或c中的任意一个字符
l) [^abc] 非a或b或c中的任意一个字符
m) [0-9a-z] 指定某个范围中的任意字符
n) [^0-9a-z] 非指定某个范围中的任意字符
o) () 正则中的分组符号
p) (?:) 只匹配 不捕获
q) (?=) 正向预查
r) (?!) 负向预查
// ---------------------------------------------
普通元字符
a) /你好/ 匹配的就是 你好 两个字符
@2. 修饰符:
常用的修饰符
a) i 忽略大小写匹配 ignoreCase
b) m 忽略换行 进行多行匹配 multiline
c) g 全局匹配 global
*/
reg = new RegExp("1从"); // 需要转义 因为是普通字符串 \\ => 真正的斜杠 \
// console.log(reg.test(",122"));
元字符解析
// 数字开始
let reg = /^\d/;
// console.log(reg.test("12a"))
// console.log(reg.test("a21"))
// 以字符结尾
reg = /[a-zA-Z]$/;
// console.log(reg.test("12a"))
// console.log(reg.test("aa1"))
// 包含数字 就符合要求
reg = /\d/;
// console.log(reg.test("a1"))
// console.log(reg.test("aa1"))
// 字符串 只能是 一个数字
reg = /^\d$/;
// console.log(reg.test("1"))
// console.log(reg.test("1a"))
// console.log(reg.test("a1a"))
// 验证手机号码: 11位
reg = /^1\d{10}$/;
// console.log(reg.test("11111111111"))
// console.log(reg.test("111111111112"))
// console.log(reg.test("21111111112"))
// 匹配小数
reg = /^\d+\.\d+$/;
// console.log(reg.test("1.2"))
// console.log(reg.test("22"))
// 转义字符
reg = /^\\d$/;
// console.log(reg.test("\\\d")) // 字符串出现转义字符 也有问题
// console.log(reg.test("\\d")) // 字符串出现转义字符 也有问题
// x|y
reg = /^12|34$/;
// console.log(reg.test("12"))
// console.log(reg.test("34"))
// console.log(reg.test("1"))// false
// console.log(reg.test("123"))
// console.log(reg.test("124"))
// console.log(reg.test("234"))
// console.log(reg.test("1234"))
// x|y的规则很乱 优先级不好说
// 一般使用 x|y 都会使用 () 进行分组 改变优先级
reg = /^(12|34)$/; // 12 | 34 之间的任意一个
// console.log(reg.test("12"))
// console.log(reg.test("34"))
// console.log(reg.test("1234"))
// [] 中括号中出现的字符 一般代表本身的含义
reg = /^[@+]+$/;
// console.log(reg.test("@")) // 全是 true
// console.log(reg.test("@+"))
// console.log(reg.test("@@@"))
// console.log(reg.test("@++"))
// console.log(reg.test("@@@@++"))
// console.log(reg.test("++++"))
// reg = /^[\d]$/; 匹配数字
// console.log(reg.test("d"))// false
// console.log(reg.test("\\"))//false
// console.log(reg.test("\\d"))//false
reg = /^[\\d]$/; //匹配 \ 或者 d
// console.log(reg.test("d"))// true
// console.log(reg.test("\\"))// true
// console.log(reg.test("\d")) // true
// console.log(reg.test("\\d")) // false
// 中括号不存在多位数
reg = /^[10-29]$/ ; //=> 表示 1 0-2 9 中的任意一个数字
console.log(reg.test("1"))
console.log(reg.test("0"))
console.log(reg.test("9"))
console.log(reg.test("2"))
console.log(reg.test("10"))
常用正则表达式
// 1. 有效数字
let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/;
// console.log(reg.test("12"))
// console.log(reg.test("+12"))
// console.log(reg.test("-12"))
// console.log(reg.test("-12."))
// console.log(reg.test("-12.0"))
// console.log(reg.test(".1"))
// console.log(reg.test("0.1"))
// console.log(reg.test("01"))
// console.log(reg.test("01.2"))
// 密码 6-16位 数字 字母 下划线
// reg = /^\w{6,16}$/;
// console.log(reg.test("123456"))
// console.log(reg.test("123456_"))
// console.log(reg.test("123456_aw"))
// console.log(reg.test("123456_000000000000000"))
// 验证姓名 中文汉字的正则 2-12
// 汉字 [\u4E00-\u9FA5]
// 译名 你好·世界
reg = /^[\u4E00-\u9FA5]{2,12}(·[\u4E00-\u9FA5]{2,12}){0,2}$/;
// 验证邮箱
/*
1. 开头 数字字母下划线
2. - . 不能连续出现 整体出现 零到多次
3. @后面 是数字字母 1到多次
4. ((\.|-)[A-Za-z0-9]+)* 匹配对@后面名字的补充 也就是多域名 @ss.com.cn
5. .com .cn 匹配域名 \.[A-Za-z0-9]+
*/
reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
// 身份证
/*
1. 18位
2. 最后一位可能是 X
3. 前六位是所在是省市县
4. 中间八位是出生年月日
5. 最后四位:
最后一位是 X 或者 数字
倒数第二位 偶数 女 奇数 男
其余是经过算法算出来的
*/
// 小括号的第二个作用 分组捕获,不仅可以匹配大正则的信息 还可以单独匹配每个小分组的内容
reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(\d|X)$/;
console.log(reg.exec("41152421000101123X"))
两种创建方式的区别
// 构造函数创建 因为传递是字符串 需要使用两个 \\ 才代表一个斜杠
// new RegExp("\\d") //=> /\d/
// 正则表达式中的部分内容是变量存储的值
// 1. 两个斜杠中间包起来的都是元字符
let type = "mao";
let reg = /^@"+type+"@$/;
console.log(reg) // /^@"+type+"@$/ 无法拼接变量的值
// 也就是说 正则中如果要包含某个变量的值 则不能使用字面量方式创建
// 需要拼接变量的话 需要使用 构造方式 拼接变量的值
reg = new RegExp("^@"+type+"@$")
console.log(reg) // /^@mao@$/
正则表达式 捕获的懒惰性
/*
* @Author: 毛毛
* @Date: 2022-03-06 20:07:21
* @Last Modified by: 毛毛
* @Last Modified time: 2022-03-06 21:01:31
*/
/*
实现正则的捕获:
1. RegExp.property的方法
@1. exec
@2. test
2. 字符串String.property上支持正则表达式处理的方法
@1. replace
@2. match
@3. split
实现正则捕获的前提是 字符串的内容需要有和正则匹配的
*/
// 基于 exec方法 实现正则的捕获:
// 结果是 null 或者是 一个数组
let str = "mao2022jun2021";
let reg = /\d+/;
/*
['2022', index: 3, input: 'mao2022jun2021', groups: undefined]
数组第一项的内容是:本次正则捕获到的内容
其余项内容:如 1 2 3索引对应的内容 会是小分组本次单独捕获到的内容
index:当前捕获内容在字符串中的起始索引
input:原始字符串
每次执行一次 exec 只能捕获到一个符合正则规则的,
但是默认情况下:我们执行一百遍 获取的结果永远都是第一个匹配到的内容,
其余的捕获不到。
=> 这就是正则捕获的懒惰性: 默认只捕获第一个
reg.lastIndex : 当前正则下一次匹配的起始索引位置
懒惰性的原因: 默认lastIndex的值不会被修改,
每一次都是从字符串起始位置查找,所以找到的永远都是第一个。
解决方式: 修饰符修改为全局匹配 g
注意:基于 test方法 进行正则匹配以后 lastIndex的值也会发生改变(基于全局修饰符g)
*/
// console.log(reg.exec(str)); // ['2022', index: 3, input: 'mao2022jun2021', groups: undefined]
// console.log(reg.exec(str));
// 设置全局修饰符 每次匹配完毕后 lastIndex会自动改变
// 没有捕获到了以后 lastIndex重置 下次捕获会从头继续开始
// TODO 注意:手动修改 lastIndex的值是无效的
// reg = /\d+/g;
// console.log(reg.exec(str), reg.lastIndex);
// console.log(reg.exec(str), reg.lastIndex);
// console.log(reg.exec(str), reg.lastIndex); // 没有捕获到了以后 lastIndex重置 下次捕获会从头继续开始
// console.log(reg.exec(str), reg.lastIndex);
// 需求 编写一个方法 execAll 执行一次 可以把所有的匹配结果给捕获到(g全局修饰符)
/**
* 执行一次 可以把所有的匹配结果给捕获到
* 正则表达式需要满足修饰符 全局匹配 否则会形成死循环
* @param {*} str 字符串
* @returns 所有匹配的结果
*/
RegExp.prototype.execAll = function execAll(str) {
// 不是全局正则匹配 只捕获一次
if(!this.global) return this.exec(str);
// str: 需要匹配的字符串
// this: 当前正则表达式
let ary = [], res; // ary 存储所有捕获的结果 res 每次捕获的结果
while (res = this.exec(str)) {
ary.push(res[0]);
}
return ary.length ? ary : null;
}
reg = /\d+/g;
console.log(reg.execAll("mao2021jun2022mao2023")); // ['2021', '2022', '2023']
// 我们编写的 execAll方法 和字符串的match方法的返回值结果一致
// 正则表达式 虽然没有直接捕获所有匹配的结果的方法 但是 字符串有 但是正则也需要设置全局匹配
console.log("mao2021jun2022mao2023".match(/\d+/g)) // ['2021', '2022', '2023']
分组捕获 和分组引用
/*
* @Author: 毛毛
* @Date: 2022-03-06 21:04:56
* @Last Modified by: 毛毛
* @Last Modified time: 2022-03-06 21:56:09
*/
/*
分组的作用:1 提高优先级 2 分组捕获 3 分组引用
分组捕获:
身份证号码
*/
let str = "42123120001205563X";
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(\d|X)$/;
// 如下两个结果 一致 数组第一项:大正则匹配的结果
// 后面的其余数组项: 每个小分组单独匹配捕获的结果
// console.log(reg.exec(str))
// console.log(str.match(reg))
// 如果使用括号 只是为了提高优先级 不是为了捕获 可以使用 (?:正则) 只匹配不捕获
reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{2})(?:\d)(?:\d|X)$/;
// console.log(reg.exec(str))
// console.log(str.match(reg))
// 纪要捕获到 {数字} 也想单独的把数字获取到 例如:第一次找到 {0} 还需要单独获取到 0
str = "{0}年{1}月{2}日";
// 使用大正则 + 小分组
reg = /\{(\d+)\}/g;
console.log(reg.exec(str))
console.log(str.match(reg)) // 多次匹配的情况下,match只能获取大正则匹配的内容
RegExp.prototype.execAllGroup = function (str) {
const arrBig = []; // 大正则匹配结果
const arrSmall = [];// 小正则匹配结果
let res;
while (res = this.exec(str)) {
const [big, small] = res;
arrBig.push(big);
arrSmall.push(small);
}
return [arrBig, arrSmall];
}
reg = /\{(\d+)\}/g;
console.log(reg.execAllGroup(str));
/*
分组的第三作用: 分组引用
是通过 \数字 让其代表和对应分组出现一模一样的内容
需要匹配同时出现某些相同的内容时 使用分组引用
*/
str = "book"; // good look 两个一样的字符同时出现
// 匹配四个字符的字符串 中间两个字符相同的
reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/;
console.log(reg.exec(str))
// console.log(reg.exec("week"))
console.log(reg.exec("deep"))
console.log(reg.exec("deeep")) // null
console.log(reg.exec("dep")) // null
console.log(reg.exec("depp")) // null
取消贪婪性
/*
* @Author: 毛毛
* @Date: 2022-03-06 22:04:49
* @Last Modified by: 毛毛
* @Last Modified time: 2022-03-06 22:18:14
*/
/*
正则捕获的贪婪性:
默认情况下:正则捕获的时候,是按照当前正则匹配的最长结果来获取的
取消贪婪性:
以最短匹配结果捕获 在量词元字符后面设置 ?
注意:问号的五大作用
问号左边是非量词元字符:本身代表量词元字符,出现 零到多次
问号左边是量词元字符: 取消正则捕获贪婪性
(?:) 只匹配不捕获
(?=) 正向预查
(?!) 负向预查
*/
let str = "mao@1234@4321mao";
// 匹配的数字 只要符合 同时捕获为最长的字符串
let reg = /\d+/g;
console.log(str.match(reg));
// 取消正则捕获贪婪性 以最短匹配结果捕获 在量词元字符后面设置 ?
reg = /\d+?/g;
console.log(str.match(reg))
其他正则捕获的方法
/*
* @Author: 毛毛
* @Date: 2022-03-07 08:24:05
* @Last Modified by: 毛毛
* @Last Modified time: 2022-03-07 09:14:27
*/
/*
1. test 方法 也能捕获 (本意是匹配)【了解即可】
使用该方法后 通过 RegExp.$1 获取第一个分组的内容 最多到 $9
也就是说最多支持9个分组的信息 且只有 test的匹配结果是true,值才会更新
*/
let str = "{0}年{1}月{2}日";
let reg = /\{(\d+)\}/g;
console.log(reg.test(str), RegExp.$1)
/*
2. replace 字符串的替换方法 一般伴随正则一起使用
不使用正则或者不进行全局正则匹配,进行替换的时候每次都是从字符串的第一个字符开始搜索替换
使用全局正则匹配,会取消正则懒惰性,每次lastIndex都会发生改变 不会每次回到第一个字符查找
*/
str = "mao@2020||mao@2022"
console.log(str.replace(/[a-zA-Z]+/g, "maomao"));
let time = "2022-03-07";
// 把字符串转为 xxxx年xx月xx日
reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
/*
$1年$2日$3日:
$1 $2 这些 都是 exec方法每次执行的结果中的小正则分组匹配到的结果
replace方法接收的参数 (正则,函数)
1. replace方法不仅把方法执行(能匹配几次 就执行几次函数,而且是
匹配一次,就立刻执行一次函数),
2. 还给方法传递了实参信息【就是exec的结果】(大正则的内容,后面是依次的小正则匹配内容)
3. 在函数中我们返回的是什么 就把当前大正则的匹配内容替换为啥
*/
time = time.replace(reg, "$1年$2日$3日");
console.log(time) // 2022年03日07日
time = "2022-03-07".replace(reg, (big, $1, $2, $3) => {
// big $1, $2, $3 等都是自己设置的变量
// console.log(big, $1, $2, $3)
return `${$1}年${$2}月${$3}日`
});
console.log(time)
time = "2022-3-7".replace(reg, (big, ...args) => {
let [$1, $2, $3] = args;
$2 = $2.padStart(2, "0");
$3 = $3.padStart(2, "0");
return `${$1}年${$2}月${$3}日`
});
console.log(time)
// 单词首字符大写
str = "good good study, day day up!";
// \b 匹配字符边界
reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
console.log(str.replace(reg, (big, ...args) => {
return args[0].toUpperCase() + big.substring(1)
}))
正则练习
出现最多的字符
let str = "dasujh,akdbasdbaisdimoisadja,,adiasdab adsads";
// 找到字符串中 那个字符出现的次数最多
/*
1. 去重
*/
// let res = {}
// Array.prototype.forEach.call(str, item=>{
// if(typeof res[item] !== "undefined") res[item]++;
// else res[item] = 1;
// // console.log(item)
// })
// console.log(res)
/*
2. 字符串排序
*/
const res = str.split("").sort((a, b) => {
return a.localeCompare(b);
})
reg = /([a-zA-Z])\1+/g;
// console.log(res.join("").match(reg).sort((a,b)=>b.length-a.length)[0].slice(0,1))
/*
3. 排序后使用正则表达式
*/
str = res.join("")
let result = [], max = 0, flag = false;
for (let i = str.length; i > 0; i--) {
r = new RegExp("([a-zA-Z])\\1{" + (i - 1) + "}", "g");
str.replace(r, (big, $1) => {
result.push($1);
max = i;
flag = true;
});
if (flag) break;
}
console.log(result, max)
时间字符串格式化
// 转为 指定格式的时间
/**
*
* @param {*} template 模板 期望的日期格式
* 模板规则: {0} 年 ...{1-5}月日时分秒
* @returns 格式化好的时间字符串
*/
function formatTime(template = "{0}年{1}月{2}日 {3}时{4}分{5}秒") {
// 获取 年月日 等信息
let timeAry = this.match(/\d+/g);
// template =
return template.replace(/\{(\d+)\}/g, (...[, $1]) => {
// 这里就是用 $1为索引 拿到时间
// let time =
return (timeAry[$1] || "0").padStart(2, "0");
// return time;
})
// console.log(timeAry)
// return template;
}
// 添加到字符串原型 (在axios中有所使用的方式)
["formatTime"].forEach(item => {
String.prototype[item] = eval(item);
})
let time = "2022-03-07 12:12:20";
// 转为 指定格式的时间
console.log(time.formatTime())
queryURLParams解析
// 转为 指定格式的时间
/**
*
* @param {*} template 模板 期望的日期格式
* 模板规则: {0} 年 ...{1-5}月日时分秒
* @returns 格式化好的时间字符串
*/
function formatTime(template = "{0}年{1}月{2}日 {3}时{4}分{5}秒") {
// 获取 年月日 等信息
let timeAry = this.match(/\d+/g);
return template.replace(/\{(\d+)\}/g, (...[, $1]) => {
return (timeAry[$1] || "0").padStart(2, "0");
})
}
// 获取URL地址问号和上面的参数信息(也可能包含HASH值)
/**
* @return 把所有问号后参数信息以键值对的方式存储起来返回
*/
function queryURLParams() {
let obj = {};
this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => obj[$1] = $2);
this.replace(/#(([^?=&#]+))/g, (...[, $1]) => obj["HASH"] = $1);
return obj;
}
// 添加到字符串原型 (在axios中有所使用的方式)
["formatTime", "queryURLParams"].forEach(item => {
String.prototype[item] = eval(item);
});
let url = "http://www.baidu.com?name=mao&age=22#abc"
console.log(url.queryURLParams())
正则表达式之千分符
// 转为 指定格式的时间
/**
*
* @param {*} template 模板 期望的日期格式
* 模板规则: {0} 年 ...{1-5}月日时分秒
* @returns 格式化好的时间字符串
*/
function formatTime(template = "{0}年{1}月{2}日 {3}时{4}分{5}秒") {
// 获取 年月日 等信息
let timeAry = this.match(/\d+/g);
return template.replace(/\{(\d+)\}/g, (...[, $1]) => {
return (timeAry[$1] || "0").padStart(2, "0");
})
}
// 获取URL地址问号和上面的参数信息(也可能包含HASH值)
/**
* @return 把所有问号后参数信息以键值对的方式存储起来返回
*/
function queryURLParams() {
let obj = {};
this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => obj[$1] = $2);
this.replace(/#(([^?=&#]+))/g, (...[, $1]) => obj["HASH"] = $1);
return obj;
}
/**
* 正则表达式之千分符 也就是大数字 转为 三个数字一个逗号分隔的形式
*/
function millimeter() {
// 需要正向预查 12, 345 678
return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content => content + ",");
}
// 添加到字符串原型 (在axios中有所使用的方式)
["formatTime", "queryURLParams", "millimeter"].forEach(item => {
String.prototype[item] = eval(item);
});
let num = "123456789"; // 123,456,789
console.log(num.millimeter())
console.log("12345678".millimeter())