JavaScript中的正则表达式
前言
本片文章是笔者个人对正则一些浅显理解,有什么错误之处,请各位留言区说明
什么是正则表达式
正则表达式就是可以验证字符串是否符合某个规则(test),也可以把字符串中符合规则的内容捕捉到(exec/match…)
正则表达式的两种创建方式
字面量创建方式
- 两个斜杠之间包起来的,都是用来描述规则的元字符
let reg = /\d+/; //=>\d代表0-9之间的数字
基于构造函数模式创建
- 有两个参数:1.元字符字符串;2.修饰符字符串(什么是元字符和修饰符后面会解释)
let reg1 = new RegExp('\\d+'); //=>/\d+/;
let reg2 = new RegExp('\\d+','g') //=>/\d+/g
- 有个小知识点需要注意下:“\”在字符串中也有特殊含义,表示转义字符
正则表达式的组成
由元字符和修饰符组成
常用元字符
- 量词元字符
元字符 | 含义 |
---|---|
* | 零到多次 |
+ | 一到多次 |
? | 零次或者一次 |
{n} | 出现n次 |
{n,} | 出现n到多次 |
{n,m} | 出现n到m次 |
- 特殊元字符
元字符 | 含义 |
---|---|
\ | 转移字符 |
. | 除\n(换行符)以外的任意字符 |
^ | 以哪一个元字符作为开始 |
$ | 以哪一个元字符作为结束 |
\n | 换行符 |
\d | 0~9之间的数字 |
\D | 非0~9之间的数字 |
\w | 数字、字母、下划线中的任意一个字符 |
\s | 一个空白字符(包含空格、制表符、换页符等) |
\t | 一个制表符(一个tab键:四个空格) |
\b | 匹配一个单词的边界 |
x|y | x或者y中的一个字符 |
[^xy] | 除x/y以外的任意字符 |
[xyz] | x或者y或者z中的一个字符 |
[a-z] | 指定a-z这个范围中的任意字符 |
[^a-z] | 非a-z中的任意字符 |
() | 正则中的分组符号 |
(?: ) | 只匹配不捕获 |
(?!) | 正向否定预查 |
(?=) | 正向肯定预查 |
- 普通元字符
元字符 | 含义 |
---|---|
/asd/ | 代表本身含义,匹配的就是字符串’asd’ |
常用修饰符
修饰符 | 含义 |
---|---|
i(ignoreCase) | 忽略单词大小写匹配 |
m(multiline) | 忽略换行符匹配即进行多行匹配 |
g(global) | 全局匹配 |
常用元字符详解
- ^ $
let reg = /^\d/;
console.log(reg.test("zhufeng")); // => false
console.log(reg.test("2020zhufeng")); // => true
console.log(reg.test("zhufeng2020")); // => false
let reg2 = /\d$/;
console.log(reg2.test("zhufeng")); // => false
console.log(reg2.test("2020zhufeng")); // => false
console.log(reg2.test("zhufeng2020")); // => true
// => ^/$两个都不加:字符串中包含符合规则的内容即可
let reg1 = /\d+/;
reg1.test("sssss2ssss"); // => true
// => ^/$都不加:字符串只能是和规则一致的内容
let reg2 = /^\d+$/;
reg2.test("202020"); // => true
reg2.test("2020ssss20"); // => false
// => 举个例子:验证手机号码(11位,第一个数字是1即可)
let reg3 = /^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")); // => false
// => 基于转移字符,让其只能代表小数点
let reg = /^2\.3$/;
console.log(reg.test("2.3")); // => true
console.log(reg.test("2@3")); // => false
console.log(reg.test("23")); // => false
// \在字符串中也有特殊含义
let str = "d";
let reg = /^\d$/;
console.log(rg.test(str)); // => false
reg = /^\\d$/;
reg.test(str); // => false
reg.test("\\d"); // => 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("1829")); // => true
console.log(reg.test("189")); // => true
console.log(reg.test("182")); // => true
// ---直接x|y会存在很乱的优先级问题,一般我们写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级 => 小括号:分组
let reg = /^18|29$/;
console.log(reg.test("18")); // => true
console.log(reg.test("29")); // => true
console.log(reg.test("129")); // => false
console.log(reg.test("1829")); // => false
console.log(reg.test("189")); // => false
console.log(reg.test("182")); // => false
- []
// []:中括号中出现的字符大部分都代表本身的含义
let reg = /^[@+]+$/; // @或者+出现一到多次
reg.test("@@"); // => true
reg.test("@+"); // => true
// []中只有\d代表本身含义
let reg = /^[\d]$/;
reg.test("d"); // => false
reg.test("\\"); // => false
reg.test("9"); // => true
// []不存在多位数
let reg = /^[18]$/; // 1或者8
reg.test("1"); // => true
reg.test("8"); // => true
reg.test("18"); // => false
reg = /^[10-29]$/; // 1或者0-2或者9
reg.test("1"); // => true
reg.test("9"); // => true
reg.test("0"); // => true
reg.test("2"); // => true
reg.test("10"); // => false
- 正负向预查
let reg = /zhufeng/;
reg.test('zhufengsss') //=>true
// 正向预查
let reg1 = /zhufeng(?=peixun)/ // zhufeng后面必须跟培训
reg1.test('zhufengsss') //=>false
reg1.test('zhufengpeixun') //=>true
reg1.test('zhufengpeixunssss') //=>true
reg1.test('zhufengssspeixunssss') //=>false
// 负向预查
let reg = /zhufeng(?!peixun)/; //zhufeng后面必须不跟peixun
console.log(reg.test('zhufengpeixun')) // false
console.log(reg.test('zhufengpei')) // true
()小分组的作用
- 提升优先级
- 分组捕获
- 分组引用
// => 分组引用
let str = "book";
let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/; // => 分组引用就是通过“\数字”让其代表和对应分组出现一摸一样的内容
console.log(reg.test("look")); // true
console.log(reg.test("deep")); // true
console.log(reg.test("book")); // true
console.log(reg.test("sonn")); // false
// => 分组捕获
// => 先总后分 先左后右 先外后里
let str = 'zhu2019zhu2020zhu2021',
reg = /\d{1,3}?/;
console.log(reg.exec(str));
reg = /(([a-z])+)((\d)+)/g;
// 这种括号套括号的情况,最小的分组永远捕获到的是符合条件的最后一项,也可以理解为把量词放在小括号外的,小分组捕获到的永远是整个分组的最后一项
console.log(reg.exec(str)); //=>["zhu2019", "zhu", "u", "2019", "9", index: 0, input: "zhu2019zhu2020zhu2021", groups: undefined]
?五大作用
- ?左边是非量词元字符:本身代表量词元字符。出现零到一次
- 问好左边是量词元字符:取消捕获时候的贪婪性
- (?: ) 只匹配不捕获
- (?=) 正向预查
- (?!) 负向预查
正则的捕获
-
实现正则捕获的方法
- 正则 RegExp.prototype 上的方法
- exec
基于 exec 实现正则的捕获
捕获到的结果是 null 或者一个数组
第一项:本次捕获到的内容
其余项:对应小分组本次单独捕获的内容
index:当前捕获内容在字符串中的起始索引
input:原始字符串 - test
RegExp.$1~RegExp.$9:获取当前本次正则匹配后,第一个到第九个分组的信息
一般不怎么用
- exec
- 字符串 String.prototype 上支持正则表达式处理的方法
- replace
@params:第一个参数正则对象,第二个参数普通字符串或者回调函数,如果是回调函数,把每次捕获到的内容作为实参传递给回调函数,把回调函数执行的返回值作为替换正则匹配的内容;
@return:被替换后的字符串。 - match
@params:正则对象(如果参数是非正则表达式对象,会默认 new RegExp(obj))
@return:数组(如果参数为空返回[’’];)
@如果传的正则表达式是全局捕获,则返回大正则每次匹配的内容,不会返回小分组匹配的内容
@如果不是全局捕获,返回第一次与大正则匹配的内容及其分组匹配的内容 - split
@params:正则对象
@return:基于正则捕获到的所有字符分割后的数组 - search
@params:正则对象,如果传入的是一个非正则表达式对象 obj,则会隐式的基于 new RegExp(obj)进行转换
@return:第一次匹配成功对应的索引,否则返回-1;
- replace
// 基于exec进行捕获 let reg = /\d+/; let str = 'asc1021dad201'; console.log(reg.lastIndex); //=>0 console.log(reg.exec(str)) //=>["1021", index: 3, input: "asc1021dad201", groups: undefined] console.log(reg.lastIndex); //=>0 第一次捕获后lastIndex的值没有改变所以,所以下一次捕获依然是从字符串最开始进行捕获 // 懒惰性的原因:默认情况下lastIndex的值不会进行修改,所以每一次都是从最开始进行捕获,任何捕获方法都是进行 // 解决方案:加全局捕获g console.log(reg.exec(str)) //=>["1021", index: 3, input: "asc1021dad201", groups: undefined] //==================================== // 基于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)) //=>false console.log(RegExp.$1); //=>'2' 存储的是上一次捕获的结果 //=>RegExp.$1~RegExp.$9:获取当前本次正则匹配后,第一个到第九个分组的信息 //============================== // 基于match进行捕获 let str = 'asc1021dad201'; str.match(/\d+/); //=>["1021", index: 3, input: "asc1021dad201", groups: undefined] let str = 'asc1021dad201'; str.match(/\d+/g); //=>["1021", "201"] // match多次匹配的情况下,match只能把大正则匹配的内容捕获到,小分组匹配的信息无法获取 let str = 'abc123adc456'; str.match(/[a-z](\d+)/g); //=>["c123", "c456"] //============================================ // 基于replace捕获:即把捕获到内容进行替换,第二个参数可以传递回调函函数,可以把正则表达式每次捕获到的内容传递给回调函数,进行相应的处理。然后把回调函数每次执行的返回值拿来替换捕获到的内容。 let time = '123-456-789'; time = time.replace(/-/g,''); console.log(time) //=> '123456789' //================================= // 基于split进行捕获:即可以选定多个字符进行替换 let str = 'dadw#frr%dd#frgg%3223s'; let ary = str.split(/[#%]/g); console.log(ary); //=>["dadw", "frr", "dd", "frgg", "3223s"]; //================================== // 基于search捕获 let str = "dadA1452"; console.log(str.search(/[^a-zA-Z]/g)); //=>4
- 正则 RegExp.prototype 上的方法
正则捕获的懒惰性
每执行一次 exec 只能捕获到一个符合正则规则的,但是默认情况下执行多少次,获取的结果永远都是第一个匹配到的,其余的捕获不到,即正则捕获的懒惰性:默认只捕获一个
- 懒惰性的原因:默认情况下 lastIndex 的值不会被修改,每一次都是从字符串开始位置找,所以找到的永远是第一个符合规则的(任何捕获方法都是这样)
- 解决正则的懒惰性用全局匹配 g
正则捕获的贪婪性
默认情况下正则捕获的时候,是按照当前正则所匹配的最长结果来获取的
- 解决贪婪性:把后面的量词元字符去掉或者在量词元字符后面加?;
let str = 'sss2010ddd1855';
let reg = /\d+/;
console.log(reg.exec(str)); //=>["2010", index: 3, input: "sss2010ddd1855", groups: undefined] 2010中2,20,201都符合/\d+/这个条件但是却匹配到了2010,这就是正则匹配的贪婪性
// 解决方案一
reg = /\d/
console.log(reg.exec(str)); //=>["2", index: 3, input: "sss2010ddd1855", groups: undefined];
// 解决方法二
reg = /\d+?/g
conosle.log(reg.exec(str)); //=>["2", index: 3, input: "sss2010ddd1855", groups: undefined]
正则表达式中的分组捕获
// => 身份证号码
let str = "620421196608034816";
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/;
console.log(reg.exec(str)); // => ["620421199410102456", "620421", "1994", "10", "10", "5", index: 0, input: "620421199410102456", groups: undefined]
console.log(str.match(reg)); // => ["620421199410102456", "620421", "1994", "10", "10", "5", index: 0, input: "620421199410102456", groups: undefined]
// =>第一项:大正则匹配结果
// =>其余项:每一个小分组单独匹配捕获的结果
// =>如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理
正则表达式的一些小案例
以下是几个比较常见的案例,也是比较基础,各位看官要是有什么更好的方法,欢迎分享
验证身份证号码
/*
*身份证号码分析
* 前六位:省市县,开头1-9
* 年:19xx ~- 20xx
* 月:01-09 10-12
* 日:01-09 10-29 29-31
* 倒数第二位性别:奇数男,偶数女
*/
let str = '620421199408034816';
let reg = /^([1-9]\d{5})((19|20)\d{2})(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{2}(\d)(?:\d|X)$/gi;
console.log(reg.exec(str)); //=>["620421199408034816", "620421", "1994", "19", "08", "03", "1", index: 0, input: "620421199408034816", groups: undefined]
验证密码
要求:必须包含大小写和数字
let str = 'dwqqcq24234ffwA#';
let reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\w\W]{8,18}$/;
console.log(reg.test(str)); //=>true
千分符
let str= '1874546577897446';
let reg = /\d{1,3}(?=(\d{3})+$)/g
/*
*为了方便分析我们匹配‘0123456789’
* \d贪婪匹配到012,进入正向肯定预查345、678,9结尾不符合结尾条件,匹配失败,回溯
* \d贪婪匹配到01,进入正向肯定预查234、567,89不符合结尾条件,匹配失败,回溯
* \d匹配0,进入正向肯定预查123,456,789,789符合结尾条件,匹配成功,lastIndex值修改进入下一轮
* ["0", "789", index: 0, input: "0123456789", groups: undefined]
* \d贪婪匹配123,进入正向肯定预查456,789,789符合结尾条件,匹配成功,lastIndex值修改进入下一轮
* ["123", "789", index: 0, input: "0123456789", groups: undefined]
* \d贪婪匹配456,进入正向肯定预查789,789符合结尾条件,匹配成功,lastIndex值修改,进入下一轮
* ["456", "789", index: 0, input: "0123456789", groups: undefined]
* \d匹配789,进入正向肯定预查匹配到‘’,失败,
* null;
* 所以返回0、123、456
*/
str = str.replace(reg,$1=>$1+',');
console.log(str); //=>1,874,546,577,897,446
url参数处理
let url = 'http://baidu.com?aa=222&dd=www&333=sss&_=der#box';
let queryURLParams = function queryURLParams(url) {
let obj= {};
url.replace(/([^#&=?]+)=([^#&=?]+)/g,(...[_,$1,$2])=>obj[$1]=$2);
url.replace(/#([^#&=?]+)/g,(...[_,$1])=>obj.HASH=$1);
return obj;
}
console.log(queryURLParams(url)); //=>{333: "sss", aa: "222", dd: "www", _: "der", HASH: "box"}