正则(regular expression)描述了一种字符串的匹配式。一般应用在一些方法中,用一些特殊的符号去代表一些特定的内容,对字符串中的信息实现查找,替换,和提取的操作。js中的正则表达式用RegExp对象表示,有两种写法:一种是字面量写法,一种是构造函数写法。
一、定义正则表达式
字面量创建方式 /正则表达式/[修饰符可写可不写]
实例创建方式 new RegExp(字符串,[修饰符])
二者的区别:字面量创建方式特殊含义的字符不需要转义,实例创建方式需要转义。
let reg = /\d+/g; reg = new RegExp("\\d+", "g");
字面量定义不能进行字符串拼接,实例创建方式可以。正则表达式中的部分内容是变量存储的值。如果正则中要包含某个变量的值,那么就不能使用字面量创建方式,因为字面量创建方式,两个斜杠包起来的都是元字符。要使用构造函数创建,因为它传递的规则是字符串,字符串是可以进行拼接的。
let str = "davina"; reg = /^@"+str+"@$/; console.log(reg.test("@davina@")); //false
console.log(reg.test('@"""strrrrr"@')); //true
reg1 = new RegExp("^@" + str + "@$"); console.log(reg1.test("@davina@")); //true
修饰符(放在第二个斜杠的后面)
i: 不区分大小写,在确定匹配项的时候忽略大小写
m: 多行模式,在到达一行文本末尾还会继续查找下一行中是否存在与模式匹配的项
g: 代表全局(global)匹配,模式将用于所有字符串,并非在发现第一个匹配项时就停止
<script> str = 'davina'; var re = /i/; var re1 = new RegExp('davina'); str1 = 'davi/na'; var re2 = '/i\//'; //注意:正则在匹配/时会有问题,需要转义
console.log(re, re1, re2); // /i/ /davina/ "/i//"
</script>
二、常用的正则字符
<script> 转义字符 1. \\ 反斜杠 2. \' 单引号 3. \" 双引号 4. \d 数字(0-9之间的任意一个数字) 5. \D 除数字以外的任意字符 6. \r 回车 7. \n 换行 8. \f 走纸换页 9. \t 横向跳格 10.\s 空格 11.\S 非空格 12.\w 数字,字母,下划线0-9 a-z A-Z 13.\W 非数字,非字母,非下划线 14.\b 单词的边界,独立的部分(字符串的开头结尾,空格的两边都是边界) 15. \B 非边界的部分 16. . 任意一个字符(代表所有) 17. \. 真正的点 中括号 18.[abc] 查找中括号中的任意字符 19.[^abc] 查找任何不在方括号之间的字符(^表示除掉某个字符) 20.[0-9] 查找任何从0-9的数字 21.[a-z] 查找任何从小写a到小写z的字符 22.[A-Z] 查找任何从大写A到大写Z的字符 [\u4e00-\u9fa5] 中文区间,包含所有的汉字 量词:一般用于限制正则的长度 23.{n} 重复n次 (正好n次) 24.{n,m} 至少重复n次,最多重复m次 25.{n,} 至少重复n次,最多不限 (n到多) 26.+ 至少重复1次,最多不限{1,} (1到多) 27.? 至少重复0次,最多重复1次{0,1}(0到1次可有可无) 28.* 至少重复0次,最多不限{0,} (0到多) 29.n$ 匹配任何结尾为n的字符串 30.^n 匹配任何开头为n的字符串 31.x|y 匹配x或者是y </script>
//元字符:^:以哪一个元字符作为开始 $:以哪一个元字符作为结束 /* let reg1 = /^\d/, reg2 = /\d$/, reg3 = /\d+/, reg4 = /^\d+$/; //^以哪一个元字符作为开始 console.log(reg1.test("2020davina")); //true //$以哪一个元字符作为结束 console.log(reg2.test("2020davina")); //false //两个都不加,字符串中包含符合规则的内容即可 console.log(reg3.test("2020davina")); //true //两个都加,字符串只能是和规则一致的内容 console.log(reg4.test("2020davina")); //false */
//元字符:\:转义 // .不是小数点,是基于\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 reg1 = /^2\.3$/; console.log(reg1.test("2.3")); //true */
//元字符:x|y :x或者y中的一个字符 /* let reg = /^18|29$/; console.log(reg.test("18")); //=>true console.log(reg.test("29")); //=>true console.log(reg.test("182")); //=>true //可以看出直接x|y会存在很乱的优先级问题,一般我们写的时候都伴随着小括号进行分组,因为小括号改变处理的优先级 reg1 = /^(18|29)$/; //只能是18或者29中的一个了 console.log(reg1.test("18")); //=>true console.log(reg1.test("29")); //=>true console.log(reg1.test("182")); //=>false */
// 元字符:[]:一般代表本身的含义 //1.中括号中出现的字符一般都代表本身的含义 /* let reg = /^[@+]$/; console.log(reg.test("@")); //=>true console.log(reg.test("+")); //=>true console.log(reg.test("@+")); //=>false reg1 = /^[\d]$/; //=>\d在中括号中还是0-9 console.log(reg1.test("d"));//=>false console.log(reg1.test("9"));//=>true //2.中括号中不存在多位数 reg2 = /^[10-29]$/; //1或者0-2或者9 console.log(reg2.test("1"));//=>true console.log(reg2.test("9"));//=>true console.log(reg2.test("0"));//=>true console.log(reg2.test("10"));//=>false */
三、正则常用的方法
1、RegExp对象相关的方法
test()方法
作用:查看正则表达式与指定字符串是否匹配,用的最多是来做判断的
语法:正则.test(字符串);
返回值:返回true/false
exec()方法
作用:返回匹配的结果,与match类似
语法:正则.exec(字符串)
返回值:数组或者是null
<script>
var str = '123dvain4553a'; var re = /a/; console.log(re.test(str)); //true
var re1 = /\d/; if (re1.test(str)) { console.log('有数字') //有数字
} else { console.log('没有数字') } var str2 = 'cat,dog,fish,chicken,duck,bat'; var re2 = /.at/; var re3 = /da/; console.log(re2.exec(str2));//["cat", index: 0, input: "cat,dog,fish,chicken,duck,bat", groups: undefined]
console.log(re3.exec(str2)); //null
</script>
2、String对象相关方法
match()方法
作用:匹配指定的字符串或者是正则,把匹配到的结果放到一个数组中
语法:字符串.match(字符串或者正则)
返回值:找到后把结果放在数组中且返回,没有找到返回null
注意:如果不带g修饰符,只会匹配一个结果,且给找到的数组增加两个属性:index(找到字符对应的位置),input(原字符串,从哪个字符串身上找)。
exec()和match()的区别:1. 一个是正则一个是String. 2. exec()只会匹配第一个符合的字符串(g对其并不起作用),match是否返回所有匹配的数组和正则里是否带g有关。
<script>
var str = 'dav2in34n345av'; var re = /v/; var re1 = /V/; console.log(str.match(re), str.match(re1)); //["v", index: 2, input: "dav2in34n345a", groups: undefined] null
var re2 = /v/g; var re3 = /\d+/g; //有量词+,所以找到的字符数量不是单个的了
console.log(str.match(re2)); // ["v", "v"]
console.log(str.match(re3));// ["2", "34", "345"]
var str1 = 'dav2in34nda345dav'; var re4 = new RegExp('da', 'g'); var re5 = /da/g; console.log(re4.exec(str1)); //['da']
console.log(str1.match(re5)); //['da','da','da']
</script>
search()方法
作用:找到匹配的字符串首次出现的位置
语法:字符串.search(字符串或者正则)
返回值:找到了返回位置的下标,没有找到返回-1
注意:search()方法的参数可以是正则,indexOf()的方法参数不可能是正则
<script>
var str = 'davina123Davian'; console.log(str.indexOf('v')); //2
console.log(str.search('v'));//2
var re = /\d/; console.log(str.indexOf(re), str.search(re));//-1 6
</script>
split()方法
作用:按约定字符串或者字符串规则拆分成数组,接受一个字符串或者正则
语法:字符串.split(字符串或者正则)
返回值:数组
<script>
var color = 'red,green,blue,white,yellow'; var color1 = color.split(/[^\,]+/); console.log(color.split(',')); //["red", "green", "blue", "white", "yellow"]
console.log(color1) // ["", ",", ",", ",", ",", ""]
</script>
replace()方法
作用:替换匹配的字符串
语法:字符串.replace(字符串或者正则,字符串或者函数)
参数:在这里参数要说明一下:参数1是字符串或者正则,要匹配的内容,与replace的第一个参数一样;参数2是要匹配的内容对应的位置下标;参数3是原字符串。
要强调的是这个函数一定要一个返回值,否则会用undefined来替代
返回值:替换后的新字符串,原来的字符串没有任何的变化
<script>
var str = 'davinadavina'; var re = /d/g; console.log(str.replace('d', 'h'), str); //havinadavina davinadavina
console.log(str.replace(re, 'h'), str); //havinahavina davinadavina
var newStr = str.replace('d', function () { return 'f'; }) console.log(newStr); //favinadavina //1. 把da全部换成*号
var re1 = /da/g; var newStr1 = str.replace(re1, function ($0, $1, $2) { console.log($0, $1, $2); //da 6 davinadavina
var str = ''; for (var i = 0; i < $0.length; i++) { str += '*'; } return str; }) console.log(newStr1) //**vina**vina //2. 去掉字符串两边的空格,包括里面的空格
var str = ' davina davina davina '; var str0 = str.replace(/\s+/g, ''); //davinadavinadavina
console.log(str0); //去掉左空格
var str1 = str.replace(/^\s*/, '');//davina davina davina
console.log(str1); //去掉右空格
var str2 = str.replace(/(\s*$)/g, ''); // davina davina davina
console.log(str2); //去掉左右空格
var str3 = str.replace(/(^\s+)|(\s+&)/g, ''); //davina davina davina
console.log(str3); // ^s+表示以空格开头的连续空白字符,s+$表示以空格结尾的连续空白字符
</script>
四、捕获
实现正则的捕获在正则RegExp.prototype上有如下方法:exec(),test();字符串String.prototype上支持正则表达式的方法有:replace(),match(),splite()行等。
正则捕获具有贪婪性和懒惰性。所谓的贪婪性就是正则在捕获时,每一次都会尽可能多的去捕获符合条件的内容。懒惰性指的是正则在成功捕获一次后不管后边的字符串有没有符合条件的都不再捕获。
贪婪性:默认情况下,正则捕获的时候,是按照当前正则所匹配的最长结果来获,如果想要取消,则只需在在量词元字符后面设置?就可。
let str = "davina2019davina2020", reg = /\d+/g, reg1 = /\d+?/g; console.log(str.match(reg)); //["2019", "2020"]
console.log(str.match(reg1)); //["2", "0", "1", "9", "2", "0", "2", "0"]
懒惰性:exec()方法中就用到了捕获,它捕获的结果是null或者是一个数组,每执行一次exec,只能捕获到一个符合正则规则的结果,但默认情况下,我们执行100遍,获取到的结果永远都是第一个匹配到的,这就是正则捕获的懒惰性引起的,默认只捕获第一个。我们实在正则捕获的前提是,当前正则要 字符串匹配,如是不匹配捕获的结果是null。
/* 第一项:本次捕获到的内容 * 其余项:对应小分组本次单独捕获的内容 * index:当前捕获内容在字符串中的起始索引 * input:原始字符串 */ let str = "davina2019davina2020", reg = /\d+/g; console.log(reg.exec(str)); //["2019", index: 6, input: "davina2019davina2020", groups: undefined] /* 正则捕获的懒惰性:默认只捕捕获第一个 */ console.log(reg.exec(str)); //["2019", index: 6, input: "davina2019davina2020", groups: undefined]
reg1 = /^\d+$/; console.log(reg.exec(str));//null
懒惰性捕获的原因是因为默认情况下lastIndex(当前正则下一次匹配起始索引位置)的值不会被修改,每一次都是从字符串开始的位置查找,所以找到的永远只是第一个。我们可以用全局修饰符g来解决这个问题。设置全局匹配修饰符g后,第一次匹配完,lastIndex会自己修改,当全部捕获完后,再次捕获的结果为null,lastIndex又回到了被始值,再次捕获又从第一个开始。
let str = "davina2019davina2020", reg = /\d+/, reg1 = /\d+/g; console.log(reg.lastIndex); //0
console.log(reg.exec(str));//["2019", index: 6, input: "davina2019davina2020", groups: undefined]
console.log(reg.lastIndex); //=>0
console.log(reg1.exec(str)); // ["2019", index: 6, input: "davina2019davina2020", groups: undefined]
console.log(reg1.lastIndex); //10
我们可以编写一个方法,执行一次可以把所有匹配的结果捕获到。
!(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; })(); let reg = /\d+/g, str = "davina2019davina2020"; console.log(reg.execAll(str)); //["2019", "2020"]
分组:量词控制之前元素的出现次数,这个元素可以是字符也可以是表达式,如果把一个表达式用括号括起来那么,这个表达式可以看成是子项(子表达式)。
如这里的/\d{2}/就表示一个分组,匹配两位数字;如果希望字符串’ab‘重复出现2次应该加上括号(ab){2}而不是ab{2}
<script>
var re = /\d{2}/; console.log(re.test('12')); //true
var re1 = /(ab){2}/; console.log(re1.test('abab'));//true
var re2 = /ab{2}/; console.log(re2.test('abab'));//false
</script>
下面为正则的分组捕获
let str = "130828199012040112"; 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)); //["130828199012040112", "130828", "1990", "12", "04", "1", index: 0, input: "130828199012040112", groups: undefined] //=>第一项:大正则匹配的结果 //=>其余项:每一个小分组单独匹配捕获的结果 //=>如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理
如果我们即要捕获到{数字},也想单独的把数字获取到,可以用以下方法:
let str = "{2020}年{11}月{11}日", reg = /\{(\d+)\}/g, Allary = [], Singleary = []; res = reg.exec(str); while (res) { let [All, Single] = res; Allary.push(All); Singleary.push(Single); res = reg.exec(str); } console.log(Allary, Singleary); //(3) ["{2020}", "{11}", "{11}"] (3) ["2020", "11", "11"]
分组引用:正则表达式中也能进行引用,分组引用就是通过“\数字”让其代表和对应分组出现一模一样的内容
let str = "book"; 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
五、断言
在正则表达式中,有些结构并不是真正匹配文本,而只负责判断在某个位置左/右是否符合要求,这种结构被称为断言,也可称为锚点。强调:断言只是条件,帮助找到真正需要的字符串,本身是不会匹配的。
1、单词边界
单词边界是指单词字符(\w)能匹配的字符串的左右位置。在java和js中,单词字符(\w)等同于[0-9a-zA-Z]。所以在这些语言中可以用\b\w+\b把所有的单词提取出来。
单词边界是\b(它其实是一个位置匹配符),它能匹配的位置:一边是单词字符\w,一边是非单词字符\W.(在\w和\W之间)。
与\b对应的还有\B,表示非单词的边界这个用的很少
<script>
var str = 'abc_123,4java=efg汉字'; var reg = /[\d\D]\b/g;["4", ",", "a", "=", "g"] console.log(str.match(reg)); var str1 = 'Wrong cannot afford defeat but right can'; var reg1 = /\b\w+\b/g; console.log(str1.match(reg1)); //["Wrong", "cannot", "afford", "defeat", "but", "right", "can"]
//如有特殊的单词如e-mail上面的正则就无法匹配要进行修改
</script>
2、^和$
常用的断言还有^和$,它们分别是匹配字符串的开始和结束位置,可以用来判断整个字符串能否由表达式匹配。^和$常用功能是删除字符串首尾多余的空白。
<script>
var str = 'first word\nsecond word\nthird word'; reg = /^\w*/ console.log(reg.exec(str)); /["first"]/
function fn(str) { reg1 = /^\s+|\s+$/
return str.replace(reg1, '') } console.log(fn(' davina ')); //davina
</script>
3、环视
环视是指在某个位置向左向右看,保证其左右位置必须出一某类字符。且环视也中同上面两个断言一样只是做判断。它在匹配过程中,不占用字符,所以被称为"零宽"。
零宽度正先行断言
(?=exp):字符出现的位置的右边必须匹配到exp这个表达式
<script>
var str = 'abcdefg'; re = /.*(?=d)/; console.log(re.exec(str)); //["abc"]
var str1 = 'davina6666'; var str2 = 'davina22222'; var reg = /davina(?=6666|4444)/; console.log(str1.match(reg), str2.match(reg));//["davina"] null
</script>
零宽度负先行断言
(?!exp) : 字符出现的位置的右边不能是exp这个表达式
<script>
var str = "#www#eee"; var reg = /(?!\#)\w+/g; console.log(str.match(reg)); //["www", "eee"]
</script>
零宽度正后发断言
(?<=exp):字符出现的位置的左边是exp这个表达式(js不支持)
零宽度负后发断言
(?<!exp):字符出现的位置的左边不能是exp这个表达式(js不支持)
总结来说,我们可以看到?号它在正则中有以下几个作用:
?左边是非量词无字符,本身代表量词元字符,出现0到1次
?左边是量词元字符:取消捕获时候的贪婪性
(?:):只匹配不捕获
(?=):正向预查
(?!):负向预查
示例:闪亮字体效果
<style> span { transition: 0.5s all ease;} </style> <body> <div id="div"> 你微笑地看着我,不说一句话。而我知道,为了这个,我已经等了很久了。 有一次,我们梦见大家都是不相识的。我们醒了,却知道我们原是相亲相爱的。 有一天,我们梦见我们相亲相爱了,我醒了,才知道我们早已经是陌路。 世界上最遥远的距离,不是生与死,而是我就站在你面前,你却不知道我爱你。 </div> <script> var val = div.innerHTML; div.innerHTML = val.replace(/./g, function (x) { return '<span>' + x + '</span>' }) setInterval(() => { var span = document.getElementsByTagName('span'); for (var i = 0; i < span.length; i++) { span[i].style.color = 'rgb(' + parseInt(Math.random() * 256) + ',' + parseInt(Math.random() * 256) + ',' + parseInt(Math.random() * 256) + ')'; } }, 500); </script> </body>
改变日期格式
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日 //=>RegExp.$1~RegExp.$9:获取当前本次正则匹配后,第一个到第九个分组的信息 */
/*方法二: //=>还可以这样处理 [str].replace([reg],[function]) 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+"日"; }); */
单词首字母大写
let str = "keep on going never give up."; let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g; //=>函数被执行了六次,每一次都把正则匹配信息传递给函数 //=>每一次arg:["keep","k"] ["on","o"] ...
str = str.replace(reg, (...arg) => { let [content, $1] = arg; $1 = $1.toUpperCase(); content = content.substring(1); return $1 + content; }); console.log(str); //Keep On Going Never Give Up.
六、常用的正则
// 1.验证是否为有效数字 /* * [-+]? => 可能出现+ -号,也可能不出现 * (\d)|([1-9]\d+) => 一位时0-9都可,多位时,首位不能是0 * \.\d+? => 小数部分可能有可能没有,一旦有小数点,后面必须要有数字 */ let reg = /^[-+]?(\d|([1-9]\d+))(\.\d+?)$/; // 2.验证密码 /* 规则:数字,字母,下划线,6-16位 */ let reg1 = /^\w{6,16}$/; //3.验证姓名 /* * 汉字:/u4E00-u9FA5/ * 长度2-10位 {2,10} * 可能有译名:·点 名字(汉译)(·[\u4E00-u9FA5]{2,10}){0,2} */ let reg2 = /^[\u4E00-u9FA5]{2,10}(·[\u4E00-u9FA5]{2,10}){0,2}$/; // 4.验证邮箱 /* * 1. 邮箱的名字是由数字,字母,_,-,.这几个部分组成,但是-/.不能连续的出现也不能作为开始 => \w+((-\w+)|\.\w+) * 2. @后面跟着:数字,字母(1到多位)[A-Za-z0-9]+ * 3. 多@后面跟着的名字的补充 =>((\.|-)[A-Za-z0-9]+)* * 多域名:.com.cn * 企业邮箱:ph.px.office.com * 4. 域名的匹配 => \.[A-Za-z0-9]+ */ let reg3 = /^\w+((-\w+)|\.\w+)*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/; // 5. 身份证号码 /* * 1. 一共18位,最后一位可能是x * 2. 前6位是省市县;中间8位是年月日;最后4位中,最后一位是X或者数字,倒数第二位性别:奇为男,偶为女;其余的算法 */ let reg4 = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/; reg4.exec('421181199411039063');//["421181199411039063", "421181", "1994", "11", "03", "6", "3"] //6. 手机号
let reg5 = /^1[34578]\d{9}$/;
~(function () { //1.千分符
/* * thousandMark:实现数字的千分符处理 * @params: * @return * [string] 千分符后的字符串 */ function thousandMark() { return this.replace(/\d{1,3}(?=(\d{3})+$)/g, (content) => content + ","); } // 还有一种方法:把字符串倒过来 // 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); //2.url的处理
/* * queryURLParams:获取url地址问号后面的参数信息(可能有hash值) * @params * @return * [object] 把所有问号参数信息以键值对的方式存储起来且返回 */ function queryURLParams() { let obj = {}, reg = /([^?=&#]+)=([^?=&#]+)/g; this.replace(reg, (...[, $1, $2]) => (obj[$1] = $2)); this.replace(/#([^?=&#]+)/g, (...[, $1]) => (obj["hash"] = $1)); return obj; } //3.时间字符串的格式化处理
/* * formatTime:时间字符串的格式化处理 * @params * templete:[string] 我们最后希望获取的日期格式的模板 */ 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; }); } /* 把所有的方法扩展到内置类String.prototype上 */ ["thousandMark", "queryURLParams", "formatTime"].forEach((item) => { String.prototype[item] = eval(item); }); })(); //进行验证
let num = "1234553423423"; //1,234,553,423,423
console.log(num.thousandMark()); let url = "http://www.davina.cn/?lx=1&from=qq#video"; console.log(url.queryURLParams()); //{lx: "1", from: "qq", hash: "video"}
let time = "2020-8-13 16:1:39"; console.log(time.formatTime()); //2020年08月13日 16时01分39秒