正则表达式(RegExp)
正则就是用来制定规则的(只能处理字符串),会在开发中节省很多代码
-
验证字符串是否符合某个规则(test)
-
把字符串中符合规则的内容捕获(exec / match)
let str = "good good study , day day up!", reg = /\d+/; reg.test(str); //=>false str = "2019-08-12"; reg.exec(str); //=>["2019",index:0,inputs:"原始字符串"]
编写正则表达式
正则创建方式
根据不同的情况用不同的创建方式
1.字面量创建方式
//=>两个斜杠之间包起来的,都是用来描述规则的元字符(不可添加变量)
let reg1 = /\d+/;
2.构造函数模式创建
//=>有两个参数:元字符字符串,修饰符字符串,中间添加变量
let reg2 = new RegExp("\\d+"+ value +"g");
两种创建方式的区别
//=>构造函数因为传递的是字符串,\需要写两个才代表斜杠
let reg1 = /\d+/g;
reg2 = new RegExp("\\d+","g");
-
两个斜杠中间包起来的都是元字符
//=>如果正则中要包含某个变量的值,则不能使用字面量方式创建 let str = "zhengyi", reg = /^@"+str+"@$/; reg.test("@zhengyi@"); //=>false reg.test('@"""strrr"@'); //=>true -
有需要变量的需求只能用构造函数创建
=>它传递的规则是字符串,这样才能进行字符串拼接 let str = "zhengyi", reg = new RegExp("^@"+str+"@$"); reg.test("@zhengyi@"); //=>true
正则表达式由两部分组成
1、元字符
下面的元字符只是常用的,没有全部写出来
普通元字符:代表本身含义的
- /zhengyi/ 此正则匹配的就是 "zhengyi"
量词元字符:设置出现的次数
- *:零到多次
- +:一到多次
- ?:零次或者一次
- {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中的一个字符
- [xyz] : x或者y或者z中的一个字符
- [^xy] : 除了x/y以外的任意字符
- [a-z] : 指定a-z这个范围中的任意字符 =>[0-9a-zA-Z_]===\w
- [^a-z] : 上一个的取反“非”
- () : 正则中的分组符号
- (?:) : 只匹配不捕获
- (?=) : 正向预查
- (?!) : 负向预查
2、修饰符
-
i (ignoreCase) : 忽略单词大小写匹配
-
m (multiline) : 可以进行多行匹配
-
g (global) : 全局匹配
/Z/.test('z') =>false /Z/i.test('z') =>true
元字符详解
1、^ $
//=>以数字开头
let reg = /^\d/;
reg.test("zheng"); //=>false
reg.test("1999zheng"); //=>true
reg.test("zheng1999"); //=>false
//=>以数字结尾
let reg = /\d$/;
reg.test("1999zheng"); //=>false
reg.test("zheng1999"); //=>true
//=>如果^/$两个都不加:字符串中包含符合规则的内容即可
let reg1 = /\d+/;
//=>如果^/$两个都加:字符串只能是和规则一致的内容
let reg2 = /^\d+$/;
//=>比如:简单的验证手机号码
let reg = /^1\d{10}$/; =>意思是:以1开头 10个数字 并且结尾必须是数字
2、\
//=>下面的 . 不是小数点,是除\n外的任意字符
let reg = /^2.3$/;
reg.test("2.3");//=>true
reg.test("2@3");//=>true
reg.test("23");//=>false
//=>基于转义字符 \ ,这里的 . 就是本意
reg = /^2\.3$/;
reg.test("2.3");//=>true
reg.test("2@3");//=>false
let str = "\\d", //=>\需要写两个才代表斜杠
reg = /^\d$/; //=>\d代表0-9的数字
reg.test(str); //=>false
reg = /^\\d$/; //=>把特殊符合转换为普通的
reg.test(str); //=>true
3、x | y
let reg = /^18|29$/;
reg.test("18"); //=>true
reg.test("29"); //=>true
reg.test("129"); //=>true
reg.test("189"); //=>true
reg.test("1829"); //=>true
reg.test("829"); //=>true
reg.test("182"); //=>true
//=>因为直接x|y会存在很乱的优先级问题,一般我们写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级
=>小括号:分组
reg = /^(18|29)$/; //=>只能是18或19
reg.test("18"); //=>true
reg.test("29"); //=>true
reg.test("129"); //=>false
reg.test("189"); //=>false
4、[]
-
1.中括号中出现的字符一般都代表本身的含义
let reg = /^[@+]$/;//=>@或+ reg.test("@"); //=>true reg.test("+"); //=>true reg.test("@@"); //=>false reg.test("@+"); //=>false reg = /^[\d]$/; //=>\d在中括号中还是0-9 reg.test("d"); //=>false reg.test("9"); //=>true -
2.中括号中不存在多位数
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
正则的捕获
实现正则捕获的方法
- 正则RegExp.prototype上的方法
- exec
- test
- 字符串String.prototype上支持正则表达式处理的方法
- replace (这个方法很重要)
- match
- splite
- ......
基于 exec 捕获
- 1、捕获的结果
- null或者一个数组
- 第一项:本次捕获到的内容
- 其余项:对应小分组本次单独捕获的内容
- index:当前捕获内容在字符串中的起始索引
- input:原始字符串
- 2、正则捕获的懒惰(默认只捕获第一个)
因为正则的懒惰型,每执行一次exec,只能捕获到一个符合正则规则的,在默认情况下, 执行n遍,获取的结果永远都是第一个匹配到的,其余的捕获不到
let str = "zhengyi1999dao2019gong20nian",
reg = /\d+/;
reg.exec(str)); //=>["1999", index: 7, input: "zhengyi1999dao2019gong20nian"];
//=>实现正则捕获的前提:当前正则要和字符串匹配,如果不匹配捕获的结果是null
reg = /^\d+$/;
reg.test(str); //=>false
reg.exec(str); //=>null
基于 test 捕获(本意是匹配)
//=>RegExp.$1~RegExp.$9:获取当前本次正则匹配后,第一个到第九个分组的信息
let str = "{0}年{1}月{2}日",
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" 存储的是上次捕获的结果
replace 字符串中实现替换的方法 ( 伴随正则使用 )
1.不用正则,replace 执行一次只能替换一个
let str = "zhengyi@1999|zhengyi@|2019";
//=>把"zhengyi"替换成"郑一"
//=>执行一次
str = str.replace("zhengyi","郑一") //=>"郑一@1999|zhengyi@|2019"
str = str.replace("zhengyi","郑一").replace("zhengyi","郑一"); //=>//=>"郑一@1999|郑一@|2019"
//2.用正则就可以全部替换,代码减少很多
str = str.replace(/zhengyi/g,"郑一");
//=>把"zhengyi"替换为"zhengyiyi"
str = str.replace("zhengyi","zhengyiyi").replace("zhengyi","zhengyiyi");
//=>"zhengyiyiyi@1999|zhengyi@|2019" 每次替换都是从字符串索引 0 开始找(类似于正则捕获的懒惰性)
//=>基于正则g可以解决
str = str.replace(/zhengyi/g,"zhengyiyi"); //=>"zhengyiyi@1999|zhengyiyi@|2019"
正则捕获的懒惰性
-
reg.lastIndex:当前正则下一次匹配的起始索引位置
-
懒惰性捕获的原因:默认情况下lastIndex的值随之更改,每次都是从字符串索引 0 重新查找,找到的永远只是第一个
-
解决办法:全局修饰符g
let str = "zhengyi1999nian05yue14ri", reg = /\d+/; console.log(reg.lastIndex); //=>0 下面匹配捕获是从str的索引零的位置开始找 console.log(reg.exec(str)); //=>["1999", index: 7, input: "zhengyi1999nian05yue14ri", groups: undefined] console.log(reg.lastIndex); //=>0 lastIndex没有改变,下次str还是从零开始查找匹配,找到的的永远是第一个匹配到的 reg = /\d+/g; //=>设置全局匹配符 g console.log(reg.exec(str)); //=>["1999",index: 7,...] console.log(reg.lastIndex); //=>11 设置全局匹配修饰符g后,第一次匹配完,lastIndex会自己修改 console.log(reg.exec(str)); //=>["05",index: 15,...] console.log(reg.lastIndex); //=>17 console.log(reg.exec(str)); //=>["14",index: 20,...] console.log(reg.lastIndex); //=>22 console.log(reg.exec(str)); //=>null 当全部捕获后,再次捕获的结果是null,lastIndex回归初始值零,再次捕获又从第一个开始 console.log(reg.lastIndex); //=>0 console.log(reg.exec(str)); //=>["1999",index: 7,...] //=>字符串符合正则规则,执行里面的代码 if (reg.test(str)) { console.log(reg.lastIndex); //=>11 因为已经基于test匹配验证,lastIndex被修改为第一次匹配后的结果 console.log(reg.exec(str)); //=>["05",index: 15,...] 从索引11开始匹配捕获 }
正则的分组捕获
-
第一项:大正则匹配的结果
-
其余项:每一个小分组单独匹配捕获的结果
-
如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理
//=>身份证号码 let str = "13032119990514133X", reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/; console.log(reg.exec(str)); console.log(str.match(reg)); //=>["13032119990514133X", "130321", "1999", "05", "14", "3", index: 0, input: "13032119990514133X"] -
分组引用
//=>匹配这种重复字母的字符串"good"、"look"、"moon"、"foot" let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/; //=>分组引用就是通过 \数字(\1) 让其代表和对应分组出现一模一样的内容 console.log(reg.test("book")); //=>true console.log(reg.test("deep")); //=>true console.log(reg.test("some")); //=>false
正则捕获的贪婪性
-
正则捕获的贪婪性:默认情况下,正则捕获时,按照当前正则所匹配的最长结果获取
let str = "zheng1999yi0514", reg = /\d+/g; console.log(str.match(reg)); //=>["1999","0514"] -
解决方法:在量词元字符后面设置 ? ,取消捕获时候的贪婪性(按照正则匹配的最短结果来获取)
reg = /\d+?/g; console.log(str.match(reg)); //=>["1", "9", "9", "9", "0", "5", "1", "4"]
问号在正则中的五大作用
- 问号左边是非量词元字符:本身代表量词元字符,出现零到一次
- 问号左边是量词元字符:取消捕获时候的贪婪性
- (?:) 只匹配不捕获
- (?=) 正向预查
- (?!) 负向预查
常用的正则表达式
1.验证是否为有效数字
/*
* 规则分析
* 1.可能出现 + - 号,也可能不出现 [+-]?
* 2.一位0-9都可以,多位首位不能是0 (\d|([1-9]\d+))
* 3.小数部分可能有可能没有,一旦有后面必须有小数点+数字 (\.\d+)?
*/
let reg = /^[+-]?(\d|([1-9]\d+))(\.\d+)?$/;
2.验证密码
//=>数字、字母、下划线
//=>6~16位
let = reg = /^\w{6,16}$/;
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
// 企业邮箱 xxx@xxx-xxx-office.com
//=> \.[A-Za-z0-9]+
//1. 这个匹配的是最后的域名(.com/.cn/.org/.edu/.net...)
5.身份证号码
/*
* 1. 一共18位
* 2. 最后一位可能是X
*
* 身份证前六位:省市县 130828
* 中间八位:年月日
* 最后四位:
* 最后一位 => X或者数字
* 倒数第二位 => 偶数 女 奇数 男
* 其余的是经过算法算出来的
*/
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;
正则使用的一些案例
1.把时间字符串进行处理
let time = "2019-08-13";
//=>变为"2019年08月13日"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
//=>这样可以实现
//time = time.replace(reg,"$1年$2月$3日");
//console.log(time); //=>2019年08月13日
//=>还可以这样处理 [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);
});
*/
time = time.replace(reg,(...arg)=>{
let [,$1,$2,$3]=arg;
$2.length<2?$2="0"+$2:null;
$3.length<2?$3="0"+$3:null;
return $1+"年"+$2+"月"+$3+"日";
});
2.单词首字母大写
let str = "good good study,day day up!";
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g;
//=>函数被执行了六次,每一次都把正则匹配信息传递给函数
//=>每一次ARG:["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!"
3.验证一个字符串中那个字母出现的次数最多,多少次?
let str = "zhengyishiqianduanxiaobai",
max = 0,
res = [],
flag = false;
str = str.split('').sort((a, b) => a.localeCompare(b)).join('');
for (let i = str.length; i > 0; i--) {
let reg = new RegExp("([a-zA-Z])\\1{" + (i - 1) + "}", "g");
str.replace(reg, (content, $1) => {
res.push($1);
max = i;
flag = true;
});
if (flag) break;
}
console.log(`出现次数最多的字符:${res},出现了${max}次`);
4.formatTime 时间字符串的格式化处理
function formatTime(templete = "{0}年{1}月{2}日 {3}时{4}分{5}秒") {
let timeAry = this.match(/\d+/g);
return templete.replace(/\{(\d+)\}/g, (...[, $1]) => {
let time = timeAry[$1] || "00";
return time.length < 2 ? "0" + time : time;
});
}
5.queryURLParams 获取URL地址问号和面的参数信息(可能也包含HASH值)
function queryURLParams() {
let obj = {};
this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => obj[$1] = $2);
this.replace(/#([^?=&#]+)/g, (...[, $1]) => obj['HASH'] = $1);
return obj;
}
6.millimeter 实现大数字的千分符处理
function millimeter() {
return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content => content + ',');
}
最后
- 有什么不正确的地方,欢迎评论指出