# 正则表达式
正则表达式是一个对象
正则表达式并不是 JS 独有的技术,基本上所有语言都支持这个技术
正则表达式的目的也很单纯,核心作用就是验证数据的合法性
正则表达式的特点:
1、正则表达式在 JS 中只针对字符串起作用
2、正则表达式会根据你设置的规则,对字符串进行 提取,搜索、替换 等操作
3、JavaScript 中正则表达式是一个内置对象,这个对象通过 RegExp () 创建,也可以直接通过赋值一个正则表达式来创建
第一种:
var reg = new RegExp(正则表达式) |
第二种:
var reg = /张/ |
方法:
1、test () 用于验证某一个字符串是否符合正则的规则
var str ="abc"; | |
var reg = /a/; | |
reg.test(str); |
2、exec()根据正则表达式提取字符串中符合要求的字符,用法与上面类似,注意:正则表达式是可以添加修饰符的,这个修饰符会影响提取字符串的结果
# 修饰符
1、g 代表 global 全局验证
2、i 代表忽略大小写
3、m 代表多行的意思,也就是可以换行匹配
# 正则表达式的规则
# 一元符号
.
匹配除换行符以外的任意符号
\w
匹配字符数字和下划线
\S
匹配任何空白符
\d
匹配所有数字,与 [0-9]
\b
匹配单词边界
|
或匹配
^
匹配字符串的开始
$
匹配字符串的结束
# 原子组和原子表
# 原子表 [ ]
var reg = /^[张王赵]三$/ | |
console.log(reg.test("张三")); //true | |
console.log(reg.test("王三")); //true | |
console.log(reg.test("赵三")); //true | |
console.log(reg.test("李三")); //false | |
console.log(reg.test("张四")); //false | |
console.log(reg.test("张三ssss")); //false | |
console.log(reg.test("张ssss三")); //false |
原子表是用中括号的形式存在,它会从这个表中拿出一个进行条件匹配
在原子表中,还可以写范围
var reg1 = /[0-9]/ | |
var reg2 = /[a-z]/ | |
var reg3 = /[A-Z]/ |
注意:原子表中的范围不能倒写,会报错
# 原子组 ( )
var reg = /张三|王三|赵三/; | |
var reg2 = /(张|王|赵)三/ // 原子组用小括号表示 |
# 反义字符
** 在 [ ] 中使用
[^x] 匹配处理 x 以外的所有字符,这里的 x 可以是任意字符
[^xyz] 同上,匹配除了 xyz 以外的字符
\W 匹配除字母、数字、下划线以外的所有字符,等同 [^\w]
\B 匹配不是单词边界的字符
# 转义字符
\xnn 匹配十六进制数
\f 匹配换页符
\n 匹配换行符
\r 匹配回车符
\unnnn 匹配 unicode 编码
# 重复匹配
*
重复匹配零次或多次
+
重复匹配 1 次或多次
?
重复匹配 0 次或 1 次
{n}
重复匹配 n 次
{n,}
至少重复匹配 n 次
{m,n}
重复普配 m 到 n 次 m < n
// 我希望验证一个字符串,它是 a 开始,c 结束,中间有 4 个英文字母。怎么写? | |
var reg = /^a[a-zA-Z]{4}c$/ | |
// 我希望验证一个字符串,它是 a 开始,c 结束,中间有 4-6 个数字。怎么写? | |
var reg1 = /^a\d{4,6}c$/ | |
// 我希望验证一个字符串,它是 a 开始,c 结束,中间至少有 4 个数字。怎么写? | |
var reg2 = /^a\d{4,}c$/ | |
// 我希望验证一个字符串,它是 a 开始,c 结束,中间至少有 1 个或多个数字。怎么写? | |
var reg3 = /^a\d+c$/ | |
// 我希望验证一个字符串,它是 a 开始,c 结束,中间至少有 0 个或多个数字。怎么写? | |
var reg4 = /^a\d*c$/ | |
// 我希望验证一个字符串,它以三结束,以张开头,中间有 0 或 1 个字符 | |
var reg5 = /^张.?三$/ |
# 贪婪与惰性
重复匹配默认都是按照贪婪的特性去匹配较多的次数,如果我们需要按照惰性特征来匹配
我们可以把上面的多有的重复匹配符号后面加上一个 ?表示惰性匹配
var str = "cawqdewfeffwerderwe"; | |
// 我要提取 c 开始到 d 结束中间任意长度的字符串 | |
var reg = /c[a-z]*d/; | |
var newStr = reg.exec(str); | |
// 这个时候我们的正则表达式处在贪婪模式下,它会执行贪婪(0 到贪婪多次) | |
var reg = /c[a-z]*?d/; // 执行惰性重复匹配 |
# 原子组编号:\n
原子组通过()形成一个分组,这个分组会给一个分组编号
var str = "<div></div>"; | |
// 现在希望通过正则验证这个标签 | |
var reg = /<[a-zA-Z]+><\/[a-zA-Z]+>/; | |
// 上面的问题在于,我们希望开始标签和结束标签里面匹配到的是相同的字符 | |
var flag = reg.test(str); |
在上面的表达式中,我们表面上看起来是完成了功能,但是又有隐患,如果我们要匹配的字符串是 <div></span>
这样也会验证成功,但是不符合要求
我们的主要需求是开始标签和结束标签要一致,我们开始匹配的内容 <[a-zA-Z]+>
要与后面匹配的 <\/[a-zA-Z]+>
保持一致,这个时候我们就要用到原子组
var str = "<div></span>"; | |
// 现在希望通过正则验证这个标签 | |
var reg1 = /<[a-zA-Z]+><\/[a-zA-Z]+>/; | |
// 上面的问题在于,我们希望开始标签和结束标签里面匹配到的是相同的字符 | |
var flag1 = reg1.test(str); | |
var reg2 = /<([a-zA-Z]+)><\/\1>/; | |
var flag2 = reg2.test(str); |
# 前瞻后顾 :()语法
首先我们要弄清除两个条件
1、你的匹配条件是什么
2、你的限制条件是什么
# 前瞻
匹配条件是 A,限制条件是 B,B 要出现在 A 的后面
A(?=B)
如果要匹配 abc 并且 abc 要在 123 的前面
/abc(?=123)/g // 全局匹配 abc 后面紧跟 123 | |
//abc123 true | |
//abc456 false | |
//123abc false |
# 负前瞻
匹配条件是 A,限制条件是 B,B 不能出现在 A 的后面
/abc(?!123)/g |
# 后顾
匹配条件是 A,限制条件是 B,B 要出现在 A 的前面
(?<=B)A |
要匹配 abc 并且 abc 要在 123 的后面
/(?<=123)abc/g | |
//abc123 false | |
//abc456 false | |
//123abc true |
# 负后顾
匹配条件是 A,限制条件是 B,B 要不能出现在 A 的前面
(?<!B)A |
# 特殊情况
如何匹配中文
var reg = /^[\u4E00-\u9FA5]{2,4}$/ |
# 正则表达式(github)
regular expression : RegExp
用来处理字符串的规则
- 只能处理字符串
- 它是一个规则:可以验证字符串是否符合某个规则 (test) ,也可以把字符串中符合规则的内容捕获到 (exec /match ...)
let str = "good good study, day day up! "; | |
//=> 学正则就是用来制定规则 (是否包含数字) | |
let reg = /\d+/; | |
reg.test(str); //=> false | |
str = "2022-08-05"; | |
reg.exec(str); //=>["2019",index:0,inputs:"原始字符串"] |
# 编写正则表达式
# 创建方式有两种
//=> 字面量创建方式 (两个斜杆之间包起来的,都是用来描述规则的元字符) | |
let reg1 = /\d+/; | |
//=> 构造函数模式创建 两个参数:元字符字符串,修饰符字符串 | |
let reg2 = new RegExp("\\d+"); |
正则表达式由两部分组成
- 元字符
- 修饰符
/* 常用的元字符 */ | |
//=>1. 量词元字符:设置出现的次数 | |
* 零到多次 | |
+ 一到多次 | |
? 零次或者一次 | |
{n} 出现n次 (n需为0或者正整数) | |
{n,} 出现n到多次 | |
{n,m} 出现n到m次 (包含n和m) | |
//=>2. 特殊元字符:单个或者组合在一起代表特殊的含义 | |
\ 转义字符(普通->特殊->普通) | |
. 除\n (换行符) 以外的任意字符 | |
^ 以哪一个元字符作为开始 | |
$ 以哪一个元字符作为结束 | |
\n 换行符 | |
\d 0~9之间的数字 | |
\D 非0~9之间的数字 (大写和小写的意思是相反的) | |
\w 数字、字母、下划线中的任意一个字符 | |
\s 一个空白字符 (包含空格、制表符、换页符等) | |
\t 一个制表符 (一个TAb键:四个空格) | |
\b 匹配一个单词的边界 | |
x|y x或者y中的一个字符 (1|5|9) | |
[xyz] x或者y或者z中的一个字符 [adghve2375] | |
[^xy] 除了x/y以外的任意字符 | |
[a-z] 指定a-z这个范围中的任意字符 [0-9a-zA-Z_](0-9或者a-z或者A-Z或者_中的一个字符) === \w | |
[^a-z] 上一个取反"非" | |
() 正则中的分组符号 | |
(?:) 只匹配不捕获 | |
(?=) 正向预查 | |
(?!) 负向预查 | |
//=>3. 普通元字符:代表本身含义 | |
/jiangbing/ 此正则匹配的就是"jiangbing" |
/* 正则表达式常用的修饰符: img*/ | |
i =>ignoreCase 忽略单词大小写匹配 | |
m =>multiline 可以进行多行匹配 | |
g =>global 全局匹配 | |
/* | |
* /A/.test('lalala'); //=>fasle | |
* /A/i.test('lalala'); //=>true | |
*/ |
# 元字符详细解析
^$
let reg = /^\d/; | |
console.log(reg.test("jiangbing")); //=>fasle | |
console.log(reg.test("2022jiangbing")); //=>true | |
console.log(reg.test("jiangbing2022")); //=>false |
let reg = /\d$/; | |
console.log(reg.test("jiangbing")); //=>fasle | |
console.log(reg.test("2022jiangbing")); //=>false | |
console.log(reg.test("jiangbing2022")); //=>true |
//=>^/$ 两个都不加:字符串中包含符合规则的内容即可 | |
let reg = /\d+/; | |
//=^/$ 两个都加:字符串只能是和规则一致的内容 | |
let reg = /^\d+$/; | |
//=> 举个例子:验证手机号码 (11 位,第一个数字是 1 即可) | |
let reg = /^1\d{10}$/; |
\
//=>. 不是小数点,是除 \n 外的任意字符 | |
let reg = /^2.3$/; | |
console.log(reg.test("2.3")); //=>true | |
console.log(reg.test("2@3")); //=>true | |
console.log(reg.test("23")); //=>fasle | |
//=> 基于转义字符,让其只能代表小数点 | |
let reg = /^2\.3$/; | |
console.log(reg.test("2.3")); //=>true | |
console.log(reg.test("2@3")); //=>false | |
let str = "\\d"; | |
reg = /^\d$/; //=>\d 代表 0-9 的数字 | |
console.log(reg.test("str")); //=>fasle | |
reg = /^\\d$/; //=> 把特殊符号转换为普通的 | |
console.log(reg.test("str")); //=>true |
x|y
let reg = /^18|29$/; | |
console.log(reg.test("18")); //=>true | |
console.log(reg.test("29")); //=>true | |
console.log(reg.test("129")); //=>true | |
console.log(reg.test("189")); //=>true | |
console.log(reg.test("1829")); //=>true | |
console.log(reg.test("829")); //=>true | |
console.log(reg.test("182")); //=>true | |
console.log(reg.test("82")); //=>fasle | |
//--- 直接 x|y 会存在很乱的优先级问题,一般我们写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级 => 小括号:分组 | |
let reg = /^(18|29)$/; | |
console.log(reg.test("18")); //=>true | |
console.log(reg.test("29")); //=>true | |
console.log(reg.test("129")); //=>fasle | |
console.log(reg.test("189")); //=>fasle | |
//=> 只能是 18 或者 29 中的一个了 |
[]
//1. 中括号中出现的字符一般都代表本身的含义 | |
let reg = /^[@+]$/; | |
console.log(reg.test("@")); //=>true | |
console.log(reg.test("+")); //=>true | |
console.log(reg.test("@@")); //=>false | |
console.log(reg.test("@+")); //=>false | |
reg = /^[\d]$/; //=>\d 在中括号中还是 0-9 | |
console.log(reg.test("d")); //=>false | |
console.log(reg.test("\\")); //=>false | |
console.log(reg.test("9")); //=>true | |
reg = /^[\\d]$/; //=>\d 已经被 \ 转义 | |
console.log(reg.test("d")); //=>true | |
console.log(reg.test("\\")); //=>true | |
console.log(reg.test("9")); //=>false | |
//2. 中括号中不存在多位数 | |
reg = /^[18]$/; | |
console.log(reg.test("1")); //=>true | |
console.log(reg.test("8")); //=>true | |
console.log(reg.test("18")); //=>false | |
reg = /^[10-29]$/; //=>1 或者 0-2 或者 9 | |
console.log(reg.test("1")); //=>true | |
console.log(reg.test("9")); //=>true | |
console.log(reg.test("0")); //=>true | |
console.log(reg.test("2")); //=>true | |
console.log(reg.test("10")); //=>false |
# 常用的正则表达式
1. 验证是否为有效数字
/* | |
* 规则分析 | |
* 1. 可能出现 + - 号,也可能不出现 [+-]? | |
* 2. 一位 0-9 都可以,多位首位不能为 0 (\d|([1-9]\d+) | |
* 3. 小数点部分可能有可能没有,一旦有后面必须有小数点 + 数字 (\.\d+)? | |
*/ | |
let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/; |
2. 验证密码
//=> 数字、字母、下划线 | |
//=>6~16 位 | |
let val = userPassInp.value, | |
reg = /^\w{6,16}$/; | |
let flag = reg.test(val); | |
/* | |
function checkPass (val){ | |
if (val.length <6 || val.length >16){ | |
alert (' 长度必须介于 6-16 位之间!'); | |
retrun; | |
} | |
let area = ['a','b'...'_']; //=> 包含数字、字母、下划线 | |
for (let i=0;i<val.length;i++){ | |
let char = val [i]; | |
if (!area.includes (char)){ | |
alert ("格式不正确"); | |
return; | |
} | |
} | |
} | |
*/ |
3. 验证真实姓名
/* | |
* 1. 验证汉字 /^[\u4E00-\u9FA5]$/ | |
* 2. 名字长度 2~10 位 | |
* 3. 可能有译名・汉字 (・[\u4E00-\u9FA5]{2,10}){0,2} | |
*/ | |
let reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10}){0,2}$/; |
4. 验证邮箱
let reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/; | |
//=> \w+((-\w+)|(\.\w+))* | |
// 1. 开头是数字字母下划线 (1 到多位) | |
// 2. 还可以是 "- 数字字母下划线 或者。数字字母下划线",整体零到多次 | |
// 邮箱的名字由 "数字、字母、下划线、-、." 几部分组成,但是 -/. 不能连续出现也不能作为开始 | |
//=> @[A-Za-z0-9]+ | |
// 1.@后面紧跟着:数字、字母 (1 - 多位) | |
//=> ((\.|-)[A-Za-z0-9]+)* | |
// 1. 对 @后面名字的补充 | |
// 多域名 .com.cn | |
// 企业域名 zxt@zhufeng-peixun-office.com | |
//=> \.[A-Za-z0-9]+ | |
// 1. @xxx.com/ @xxx.cn 这个匹配的是最后的域名 (.com/.cn/.org/.edu/.net/.vip...) |
5. 身份证号码
/* | |
* 1. 一共 18 位 | |
* 2. 最后一位可能是 X | |
* | |
* 身份证前六位:省市县 130828 | |
* 中间八位:年月日 | |
* 最后四位: | |
* 最后一位 => X 或者数字 | |
* 倒数第二位 => 偶数 女 奇数 男 | |
* 其余的是经过算法算出来的 | |
*/ | |
//let reg = /^\d{17}(\d|X)$/; | |
//=> 小括号分组的第二个作用:分组捕获,不仅可以把大正则匹配的信息捕获到,还可以单独捕获到每个小分组的内容 | |
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/; | |
reg.exec("130828199012040617");//=>['130828199012040617', '130828', '1990', '12', '04', '1', '7', index: 0, input: '130828199012040617', groups: undefined] 捕获结果是一个数组,包含每一个小分组单独获取的内容 |
正则两种创建方式的区别
//=> 构造函数因为传递的是字符串,\ 需要写两个才代表斜杆 | |
let reg = /\d+/g; | |
reg = new RegExp("\\d+","g"); | |
//=> 正则表达式中的部分内容是变量存储的值 | |
// 1. 两个斜杆中间包起来的都是元字符 (如果正则中要包含某个变量的值,则不能使用字面量方式创建) | |
let type = 'zhufeng'; | |
reg = /^@"+type+"@$/; | |
console.log(reg.test("@zhufeng@")); //=>false | |
console.log(reg.test('@"""typeeee"@')); //=true | |
// 2. 这种情况只能使用构造函数方式 (因为它传递的规则是字符串,只有这样才能进行字符拼接) | |
reg = new RegExp("^@"+type+"@$"); | |
console.log(reg.test("@zhufeng@")); //=>true |
# 正则的捕获
实现正则捕获的办法
- 正则 RegExp.prototype 上的方法
- exec
- test
- 字符串 String.prototype 上支持正则表达式处理的方法
- replace
- match
- splite
- ......
let str = "zhufeng2022yangfan2023qihang2024"; | |
let reg = /\d+/; | |
/* | |
* 基于 exec 实现正则的捕获 | |
* 1. 捕获到的结果是 null 或者一个数组 | |
* 数组第一项:本次捕获到的内容 | |
* 其余项:对应小分组本次单独捕获的内容 | |
* index: 当前捕获内容在字符串中的起始索引 | |
* input: 原始字符串 | |
* 2. 每执行一次 exec,只能捕获到一个符合正则规则的,但是默认情况下,我们执行 100 遍,获取的结果永远都是第一个匹配到的,其余的捕获不到 | |
* =>"正则捕获的懒惰性": 默认只捕获第一个 | |
*/ | |
console.log(reg.exec(str)); //=>['2022', index: 7, input: 'zhufeng2022yangfan2023qihang2023'] | |
/* | |
//=> 实现正则捕获的前提是:当前正则要和字符串匹配,如果不匹配捕获的结果是 null | |
let reg = /^\d+$/; | |
console.log (reg.test (str)); //=>fasle | |
console.log (reg.exec (str)); //=>null |
# 懒做性的解决办法
let str = "zhufeng2022yangfan2023qihang2024"; | |
let reg = /\d+/; | |
/* | |
* reg.lastIndex: 当前正则下一次匹配的起始索引位置 | |
* 懒惰性捕获的原因:默认情况下 lastIndex 的值不会被修改,每一次都是从字符串开始位置查找,所以找到的永远只是第一个 | |
* 解决办法:全局修饰符 g | |
*/ | |
/* console.log (reg.lastIndex); //=>0 下面匹配捕获是从 str 索引零的位置开始找 | |
console.log (reg.exec (str)); | |
console.log (reg.lastIndex); //=>0 第一次匹配捕获完成,lastIndex 没有改变,所以下一次 exec 依然是从字符串最开始找,找到的永远是第一个匹配到的 | |
*/ | |
/* reg = /\d+/g; | |
console.log (reg.exec (str)); //=>["2022"...] | |
console.log (reg.lastIndex); //=>11 设置全局匹配修饰符 g 后,第一次匹配完,lastIndex 会自己修改 | |
console.log (reg.exec (str)); //=>["2023"...] | |
console.log (reg.lastIndex); //=>22 | |
console.log (reg.exec (str)); //=>["2024"...] | |
console.log (reg.lastIndex); //=>32 | |
console.log (reg.exec (str)); //=>null 当全部捕获后,再次捕获的结果是 null,但是 lastIndex 又回归了初始值零,再次捕获又从第一个开始了... | |
console.log (reg.lastIndex); //=>0 | |
console.log (reg.exec (str)); //=>["2019"...] | |
*/ | |
/* reg = /\d+/g; | |
if (reg.test (str)) { | |
//=> 验证一下:只有正则和字符串匹配我们再捕获 | |
console.log (reg.lastIndex); //=>11 基于 test 匹配验证后,lastIndex 已经被修改为第一次匹配后的结果,所以下一次捕获不再从头开始 | |
console.log (reg.exec (str)); //=>["2023"...] | |
} | |
*/ | |
//=> 需求:编写一个 execAll,执行一次可以把所有匹配的结果捕获到 (前提正则一定要设置全局修饰符 g) | |
~ function() { | |
function execAll(str = "") { | |
//=>str: 需要匹配的字符串 | |
//=>this: RegExp 的实例 (当前操作的正则) | |
//=> 进来后第一件事,是验证当前正则是否设置了 g,不设置则不能在进行循环捕获了,否则会导致死循环 | |
if (!this.global) return this.exec(str); | |
//=>ary 存储最后所有捕获的信息 res 存储每一次捕获的内容 | |
let ary = [], | |
res = this.exec(str); | |
while (res) { | |
//=> 把每一次捕获的内容 res [0] 存放到数组中 | |
ary.push(res[0]); | |
//=> 只要捕获的内容不为 null,则继续捕获下去 | |
res = this.exec(str); | |
} | |
return ary.length === 0 ? null : ary; | |
} | |
RegExp.prototype.execAll = execAll; | |
}(); | |
reg = /\d+/g; | |
console.log(reg.execAll(str)); | |
//=> 字符串中的 match 方法,可以在执行一次的情况下,捕获到所有匹配的数据 (前提:正则也得设置 g 才可以) | |
console.log(str.match(reg)); |
# 正则的分组捕获
//=> 身份证号码 | |
let str = "130828199012040112"; | |
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/; | |
console.log(reg.exec(str)); //=>['130828199012040112', '130828', '1990', '12', '04', '1', index: 0, input: '130828199012040112'] | |
console.log(str.match(reg)); //=>['130828199012040112', '130828', '1990', '12', '04', '1', index: 0, input: '130828199012040112'] | |
//=> 第一项:大正则匹配的结果 | |
//=> 其余项:每一个小分组单独匹配捕获的结果 | |
//=> 如果设置了分组 (改变优先级),但是捕获的时候不需要单独捕获,可以基于?: 来处理 |
//=> 既要捕获到 {数字},也想单独的把数字也获取到,例如:第一次找到 {0} 还需单独获取 0 | |
let str = "{0}年{1}月{2}日"; | |
/* | |
//=> 不设置 g 只匹配一次,exec 和 match 获取的结果一致 (既有大正则匹配的信息,也有小分组匹配的信息) | |
let reg = /\{(\d+)\}/; | |
console.log (reg.exec (str)); //=>['{0}','0',...] | |
console.log (str.match (reg)); //=>['{0}','0',...] | |
*/ | |
let reg = /\{(\d+)\}/g; | |
//console.log (str.match (reg)); //=> ['{0}', '{1}', '{2}'] 多次匹配的情况下,match 只能把大正则匹配的内容获取到,小分组匹配的信息无法获取 | |
let aryBig = [], | |
arySmall = [], | |
res = reg.exec(str); | |
while(res){ | |
let [big,small] = res; | |
aryBig.push(big); | |
arySmall.push(small); | |
res = reg.exec(str); | |
} | |
console.log(aryBig,arySmall); //=>['{0}', '{1}', '{2}'] (3) ['0', '1', '2'] |
//=> 分组的第三个作用: "分组引用" | |
let str = "book"; //=>"good"、"look"、"moon"、"foot"... | |
let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/; //=> 分组引用就是通过 "\ 数字" 让其代表和对应分组出现一模一样的内容 | |
console.log(reg.test("book")); //=>true | |
console.log(reg.test("deep")); //=>true | |
console.log(reg.test("some")); //=>false |
# 正则捕获的贪婪性
let str = "珠峰2022@2023培训"; | |
//=> 正则捕获的贪婪性:默认情况下,正则捕获的时候,是按照当前正则所匹配的最长结果来获取的 | |
let reg = /\d+/g; | |
console.log(str.match(reg)); //=>["2019","2022"] | |
//=> 在量词元字符后面设置?: 取消捕获时候的贪婪性 (按照正则匹配的最短结果来获取) | |
reg = /\d+?/g; | |
console.log(str.match(reg)); //=>['2', '0', '2', '2', '2', '0', '2', '3'] |
? 问号在正则中的五大作用
- 问号左边是非量词元字符:本身代表量词元字符,出现零到一次
- 问号左边是量词元字符:取消捕获时候的贪婪性
- (?😃 只匹配不捕获
- (?=) 正向预查
- (?!) 负向预查
# 其他正则捕获的方法
1.test 也能捕获 (本意是匹配)
let str = "{0}年{1}月{2}日"; | |
let reg = /\{(\d+)\}/g; | |
console.log(reg.test(str)); //=>true | |
console.log(RegExp.$1); //=>"0" | |
console.log(reg.test(str)); //=>true | |
console.log(RegExp.$1); //=>"1" | |
console.log(reg.test(str)); //=>true | |
console.log(RegExp.$1); //=>"2" | |
console.log(reg.test(str)); //=>fasle | |
console.log(RegExp.$1); //=>"2" 存储的是上次捕获的结果 | |
//=>RegExp.$1~RegExp.$9: 获取当前本次正则匹配后,第一个到第九个分组的信息 |
2.replace 字符串中实现替换的方法 (一般都是伴随正则一起使用的)
let str = "zhufeng@2022|zhufeng@2023"; | |
//=> 把 "zhufeng" 替换成 "珠峰" | |
// 1. 不用正则,执行一次只能替换一个 | |
str = str.replace("zhufeng","珠峰").replace("zhufeng","珠峰"); | |
console.log(str); //=>"珠峰 @2022 | 珠峰 @2023" | |
// 2. 使用正则会简单点 | |
str = str.replace(/zhufeng/g,"珠峰"); | |
console.log(str); //=>"珠峰 @2022 | 珠峰 @2023" |
let str = "zhufeng@2022|zhufeng@2023"; | |
//=> 把 "zhufeng" 替换成 "zhufengpeixun" | |
str = str.replace("zhufeng","zhufengpeixun").replace("zhufeng","zhufengpeixun"); | |
console.log(str); //=>"zhufengpeixunpeixun@2022|zhufeng@2023" 每一次替换都是从字符串第一个位置开始找的 (类似于正则捕获的懒惰性) | |
//=> 基于正则 g 可以实现 | |
str = str.replace(/zhufeng/g,"zhufengpeixun"); | |
console.log(str); //=>"zhufengpeixun@2022|zhufengpeixun@2023" |
案例:把时间字符串进行处理
let time = "2022-08-09"; | |
//=> 变为 "2022 年 08 月 09 日" | |
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/; | |
//=> 这样可以实现 | |
//time = time.replace (reg,"$1 年 $2 月 $3 日"); | |
//console.log (time); //=>"2022 年 08 月 09 日" | |
//=> 还可以这样处理 [str].replace ([reg],[function]) | |
// 1. 首先拿 reg 和 time 进行匹配捕获,能匹配到几次就会把传递的函数执行几次 (而且是匹配一次就执行一次) | |
// 2. 不仅把方法执行,而且 replace 还给方法传递了实参信息 (和 exec 捕获的内容一致的信息:大正则匹配的内容,小分组匹配的信息....) | |
// 3. 在函数中我们返回的是啥,就把当前大正则匹配的内容替换成啥 | |
/* | |
time = time.replace (reg,(big,$1,$2,$3)=>{ | |
//=> 这里的 $1~$3 是我们自己设置的变量 | |
console.log (big,$1,$2,$3); //=>"2022-08-09 2022 08 09" | |
}); | |
*/ | |
time = time.replace(reg,(...arg)=>{ | |
console.log(arg); | |
let [,$1,$2,$3] = arg; | |
$2.length<2 ? $2="0"+$2 : null; | |
$3.length<2 ? $3="0"+$3 : null; | |
return $1+"年"+$2+"月"+$3+"日"; | |
}); |
单词首字母大写
let str = "good good study, day day up! "; | |
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g; | |
//=> 函数被执行了六次,每一次都把正则匹配信息传递给函数 | |
//=> 每一次 arg: ['good','g'] ['good','g'] ['good','g'] ['study','s']... | |
str = str.replace(reg,(...arg)=>{ | |
let [content,$1] = arg; | |
$1 = $1.toUpperCase(); | |
content = content.substring(1); | |
return $1 + content; | |
}); | |
console.log(str); //=>"Good Good Study, Day Day Up! " |
验证一个字符串中哪个字母出现的次数最多,多少次?
let str = "zhufengpeixunzhoulaoshi"; | |
/* | |
* 去重思维 | |
*/ | |
let obj = {}; | |
[].forEach.call(str,char=>{ | |
if(typeof obj[char] !== "undefined"){ | |
obj[char++]; | |
return; | |
} | |
obj[char] = 1; | |
}); | |
console.log(obj);let obj = {}; | |
[].forEach.call(str, char => { | |
if (typeof obj[char] !== "undefined") { | |
obj[char]++; | |
return; | |
} | |
obj[char] = 1; | |
}); | |
let max = 1, | |
res = []; | |
for (let key in obj) { | |
let item = obj[key]; | |
item > max ? max = item : null; | |
} | |
for (let key in obj) { | |
let item = obj[key]; | |
if (item === max) { | |
res.push(key); | |
} | |
} | |
console.log(`出现次数最多的字符是:${res},出现了${max}次`); //=> 出现次数最多的字符是:h,u,出现了 3 次 | |
/* | |
* 排序 | |
*/ | |
str = str.split('').sort((a, b) => a.localeCompare(b)).join(''); | |
//console.log(str); //=>"aeefghhhiilnnoopsuuuxzz" | |
let reg = /([a-zA-Z])\1+/g; | |
let ary = str.match(reg); //=>['ee', 'hhh', 'ii', 'nn', 'oo', 'uuu', 'zz'] | |
ary.sort((a, b) => b.length - a.length); | |
console.log(ary); //=>['hhh', 'uuu', 'ee', 'ii', 'nn', 'oo', 'zz'] | |
let max = ary[0].length, | |
res = [ary[0].substr(0, 1)]; | |
for (let i = 1; i < ary.length; i++) { | |
let item = ary[i]; | |
if (item.length < max) { | |
break; | |
} | |
res.push(item.substr(0, 1)); | |
} | |
console.log(`出现次数最多的字符是:${res},出现了${max}次`); //=> 出现次数最多的字符是:h,u,出现了 3 次 | |
/* | |
* 正则 | |
*/ | |
let max = 0, | |
res = [], | |
flag = false; | |
str = str.split('').sort((a, b) => a.localeCompare(b)).join(''); | |
// console.log(str); //=>"aeefghhhiilnnoopsuuuxzz" | |
for (let i = str.length; i > 0; i--) { | |
let reg = new RegExp("([a-zA-Z])\\1{" + (i - 1) + "}", "g"); | |
// i=3 /([a-zA-Z])\1{2}/ "hhh" "uuu" | |
str.replace(reg, (content, $1) => { | |
res.push($1); | |
max = i; | |
flag = true; | |
}); | |
if (flag) break; | |
} | |
console.log(`出现次数最多的字符是:${res},出现了${max}次`); //=> 出现次数最多的字符是:h,u,出现了 3 次 |
时间字符串格式化
~ function() { | |
/* | |
* formatTime: 时间字符串的格式化处理 | |
*/ | |
/* function formatTime () { | |
// 1. 首先获取时间字符串中的年月日等信息 | |
let timeAry = this.match (/\d+/g); | |
//console.log (timeAry)//['2022', '8', '10', '16', '00', '43'] | |
let template = "{0} 年 {1} 月 {2} 日 {3} 时 {4} 分 {5} 秒"; | |
template = template.replace (/\{(\d+)\}/g, (content, $1) => { | |
//=>content: 当前本次大正则匹配的信息 $1: 本次小分组单独匹配的信息 | |
// 以 $1 的值作为索引,到 timeAry 中找到对应的时间 (如果没有则用 "00" 补) | |
let time = timeAry [$1] || "00"; | |
time.length < 2 ? time = "0" + time : null; | |
return time; | |
}) | |
return template; | |
}*/ | |
//=> 简化版本 | |
/* | |
* formatTime: 时间字符串的格式化处理 | |
* @params | |
* template: [string] 我们最后期望获取日期格式的模板 | |
* 模板规则: {0}-> 年 {1~5}-> 月日时分秒 | |
* @return | |
* [string] 格式化后的时间字符串 | |
* by 江冰 on 2022-8-10 16:33:43 | |
*/ | |
function formatTime(template = "{0}年{1}月{2}日 {3}时{4}分{5}秒") { | |
let timeAry = this.match(/\d+/g); | |
return template = template.replace(/\{(\d+)\}/g, (...[, $1]) => { | |
let time = timeAry[$1] || "00"; | |
return time.length < 2 ? time = "0" + time : time; | |
}); | |
} | |
/* 扩展到内置类 String.prototype 上 */ | |
["formatTime"].forEach(item => { | |
String.prototype[item] = eval(item); | |
}); | |
}(); | |
//=> 服务器获取的 | |
// "2022-8-10 16:00:43" | |
// "2022/8/10 16:00:43" | |
// "2022/8/10" | |
//=> 想要转变为的格式 | |
// "08 月 10 日 16 时 00 分" | |
// "2022 年 8 月 10 日" | |
// ... | |
let time = "2022-8-10 16:00:43"; | |
console.log(time.formatTime()); //=>2022 年 08 月 10 日 16 时 00 分 43 秒 | |
console.log(time.formatTime("{0}年{1}月{2}日")); //=>2022 年 08 月 10 日 | |
console.log(time.formatTime("{1}-{2} {3}:{4}")); //=>08-10 16:00 | |
time = "2022/8/13"; | |
console.log(time.formatTime()); //=>2022 年 08 月 13 日 00 时 00 分 00 秒 | |
console.log(time.formatTime("{0}年{1}月{2}日")); //=>2022 年 08 月 13 日 | |
console.log(time.formatTime("{1}-{2} {3}:{4}")); //=>08-13 00:00 |
queryURLParams
~ function() { | |
/* | |
* queryURLParams: 获取 URl 地址问号和后面的参数信息 (可能也包含 HASH 值) | |
* @params | |
* @return | |
* [object] 把所有问号参数信息以键值对的方式存储起来并且返回 | |
* by 江冰 on 2022-8-10 16:33:43 | |
*/ | |
function queryURLParams() { | |
let obj = {}; | |
this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => obj[$1] = $2); | |
this.replace(/#([^?=&#]+)/g, (...[, $1]) => obj['HASH'] = $1); | |
return obj; | |
} | |
/* 扩展到内置类 String.prototype 上 */ | |
["queryURLParams"].forEach(item => { | |
String.prototype[item] = eval(item); | |
}); | |
}(); | |
let url = "http://www.zhufengpeixun.cn/?lx=1&from=wx#video"; | |
console.log(url.queryURLParams()); //=>{lx: '1', from: 'wx', HASH: 'video'} |
千分符
~ function() { | |
/* | |
* millimeter: 实现大数字的千分符处理 | |
* @params | |
* @return | |
* [string] 千分符后的字符串 | |
* by 江冰 on 2022-8-10 16:33:43 | |
*/ | |
function millimeter() { | |
return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content => content + ','); | |
} | |
/* 扩展到内置类 String.prototype 上 */ | |
["millimeter"].forEach(item => { | |
String.prototype[item] = eval(item); | |
}); | |
}(); | |
let num = "15628954"; //=>"15,628,954" 千分符 | |
console.log(num.millimeter()); //=>"15,628,954" | |
// 把字符串倒过来 | |
num = num.split('').reverse().join(''); | |
for (let i = 2; i < num.length - 1; i += 4) { | |
let prev = num.substring(0, i + 1), | |
next = num.substring(i + 1); | |
num = prev + "," + next; | |
} | |
num = num.split('').reverse().join(''); | |
console.log(num); //=>"15,628,954" |
# 常用的正则表达式
// 正整数 | |
/^[0-9]*[1-9][0-9]*$/; | |
// 负整数 | |
/^-[0-9]*[1-9][0-9]*$/; | |
// 正浮点数 | |
/^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$/; | |
// 负浮点数 | |
/^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/; | |
// 浮点数 | |
/^(-?\d+)(\.\d+)?$/; | |
//email 地址 | |
/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/; | |
//url 地址 | |
/^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$/; | |
或:^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$ | |
// 年 / 月 / 日(年 - 月 - 日、年。月。日) | |
/^(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$/; | |
// 匹配中文字符 | |
/[\u4e00-\u9fa5]/; | |
// 匹配帐号是否合法 (字母开头,允许 5-10 字节,允许字母数字下划线) | |
/^[a-zA-Z][a-zA-Z0-9_]{4,9}$/; | |
// 匹配空白行的正则表达式 | |
/\n\s*\r/; | |
// 匹配中国邮政编码 | |
/[1-9]\d{5}(?!\d)/; | |
// 匹配身份证 | |
/\d{15}|\d{18}/; | |
// 匹配国内电话号码 | |
/(\d{3}-|\d{4}-)?(\d{8}|\d{7})?/; | |
// 匹配 IP 地址 | |
/((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/; | |
// 匹配首尾空白字符的正则表达式 | |
/^\s*|\s*$/; | |
// 匹配 HTML 标记的正则表达式 | |
< (\S*?)[^>]*>.*?|< .*? />; | |
//sql 语句 | |
^(select|drop|delete|create|update|insert).*$ | |
// 提取信息中的网络链接 | |
(h|H)(r|R)(e|E)(f|F) *= *('|")?(\w|\\|\/|\.)+('|"| *|>)? | |
// 提取信息中的邮件地址 | |
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* | |
// 提取信息中的图片链接 | |
(s|S)(r|R)(c|C) *= *('|")?(\w|\\|\/|\.)+('|"| *|>)? | |
// 提取信息中的 IP 地址 | |
(\d+)\.(\d+)\.(\d+)\.(\d+) | |
// 取信息中的中国手机号码 | |
(86)*0*13\d{9} | |
// 提取信息中的中国邮政编码 | |
[1-9]{1}(\d+){5} | |
// 提取信息中的浮点数(即小数) | |
(-?\d*)\.?\d+ | |
// 提取信息中的任何数字 | |
(-?\d*)(\.\d+)? | |
// 电话区号 | |
^0\d{2,3}$ | |
// 腾讯 QQ 号 | |
^[1-9]*[1-9][0-9]*$ | |
// 帐号(字母开头,允许 5-16 字节,允许字母数字下划线) | |
^[a-zA-Z][a-zA-Z0-9_]{4,15}$ | |
// 中文、英文、数字及下划线 | |
^[\u4e00-\u9fa5_a-zA-Z0-9]+$ |