一、什么是正则?
正则就是一个规则,用来处理 字符串 的规则
1.正则匹配
编写一个规则,验证某个字符串是否符合这个规则,正则匹配使用的是test方法
2.正则捕获
编写一个规则,在一个字符串中把符合规则的内容都获取到,正则捕获使用的方法:正则的exec()、split()、replace()、match()等方法都支持正则
var reg = / ^$/ //两个斜杠中间包含一些内容就是正则,两个斜杠之间包含的全部内容都是元字符
二、正则的元字符和修饰符
任何一个正则都是由元字符和修饰符组成的
1、修饰符
g:global全局匹配
i:ignoreCase忽略单词大小写的匹配
m:multiline多行匹配
2、元字符
①:量词元字符
+:让前面的元字符出现一到多次
?:出现0到1次
*:出现0到多次
{n}:出现n次
{n,}:出现n到多次
{n,m}:出现n到m次
②:特殊意义的元字符
\:转义字符(把一个普通字符转变为有特殊意义的字符,或者把一个有意义字符转换为普通字符)
. :除了\n(换行符)以外的任意字符
\d:匹配一个0-9之间的数字
\D:匹配任意一个非0-9之间的数字(大写字母和小写字母的组合正好是反向的)
\w:匹配一个0-9或字母或 _之间的字符
\s:匹配一个任意空白字符
\b:匹配一个边界符
x|y:匹配x或者y中一个
[a-z]:匹配a-z中的任意一个字符
[^a-z]:和上面相反,匹配任意一个非a-z的字符
[xyz]:匹配x或者y或者z中的一个字符
[^xyz]:匹配除了xyz以外的任意字符
():正则的小分组,匹配一个小分组(小分组可以理解为大正则中的一个小正则)
^:以某个元字符开始
$:以某一个元字符结束
?: :只匹配不捕获
?=:正向预查
?!:反向预查
除了以上特殊元字符和量词元字符,其余的都叫做普通元字符,代表本身意义的元字符
三、元字符详细解读
①:^ $
var reg = /\d+/ var a ='alhh是最棒的2019' reg.test(a) //true var reg = /^\d+/ var a ='alhh是最棒的2019' reg.test(a) //false var reg = / ^\d$/ var a = '0910是最棒的2019' reg.test(a) //false 中间是汉字 //在中括号 ^表示非 否则是以某某开头 // reg = /^\d+$/ 只能是某某某 这里说明只能是1到多个数字 不能出现汉字 // ^和$ 只是一个修饰或者声明,不会占据字符串的位置
②:\
var reg = /^2.3$/ reg.test('2.3') //true reg.test('2+3') //true //点在正则中的意思:匹配除了\n以为的任意字符,而不是单纯的小数点 //如果想代表小数点 reg = /^2\.3$/ var reg = /^d$/ //匹配字母d var reg = /^\d$/ //匹配0-9 var reg =/^\\d$/ //匹配\\d var reg =/^\\\d$/ //匹配\\0-9 //一个/就是转义 两个//就普通的两个//
③:x|y
var reg = /^18|19$/ //18 19 189 119 181 819 1819 都符合这个规则 //以1开头,以9结尾中间是8或者1;以18开头或者以19结尾即可 var reg = /^(18|19)$/ //只能是18或者19
④:()
正则中的分组,也可以理解为一个大正则中的一个正则(包起来的部分是一个整体);在正则中,我们可以使用小括号 改变一些默认的优先级
小分组还有第二个作用:分组引用
第三个作用:分组捕获
var reg = /^([a-z])([a-z])\2([a-z])$/ var a = 'foot' //true //分组引用:\1 或者 \2 ...出现和第N个分组一模一样的内容
⑤:[]
[xyz][^xyz][a-z][^a-z] [a-zA_Z0-9_] == \w
中括号中出现的元字符,一般都代表本身的含义
var reg = /^[.?+&]+$/ //里面的四个元字符都是本身的含义,例如:点就是小数点,不是所谓的任意字符。。。 需求:描述样式类名的规则(数字、字母、下划线),并且不能以-开头 var reg = /^[0-9a-zA-z_-]+$/ //没有处理以-开头的情况 var reg = /^\w[\w-]*$/ //满足
四:练习
1:需求:验证18-65之间的年龄
var reg = /^[18-65]$/ //1或者8 6或者5的任意一个字符,中括号中出现的18不是数字18,而是1或者8 当前正则是非法的,因为不能设置8~6这种范围 var reg = /^[(18)-(65)]&/ //也是错的 分三阶段处理 //18或者19 // 20-59 // 60-65 var reg = /^((18|19)|([2-5]\d)|(6[0-5]))$/
2:常用的正则表达式编写
①:验证是否为有效数字
分析:可能是正数,负数; 整数或者小数 只要出现小数点,后面至少要跟一位数字 小数点前面必须有数字 var reg = /^-?(\d|([1-9]\d+))(\.\d+)?$/ // -?负号可有可无 // \d一位数可以是任何值 // (\d|([1-9]\d+)) 多位数不能以0开头 // (\.\d+)? 小数部分可有可无,有的话后面必须跟一位数字 只能为小数 var reg = /^(\d|([1-9]\d+))(\.\d+)$/ 只能是正整数 var reg = /^(\d|([1-9]\d+))$/ 不能是小数 var reg = /^-?(\d|([1-9]\d+))$/
②:手机号码&真实姓名&邮箱
分析:11位数字 1开头 var reg = /^1\d{10}$/ //用户名:真实姓名 /^[\u4E00-\u9FA5]$/ 验证中文汉字 var reg = /^[\u4E00-\u9FA5]{2,5}(·[\u4E00-\u9FA5]{2,5})?$/ //邮箱 var reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/ //以数字字母下划线 //@前面可以是数字、字母、下划线、-、. 这些符号 //不能把-和. 连续出现,出现一次后面必须跟数字字面下划线
③:身份证号码
分析:18位 前17位必须是数字 最后一位可以是数字或者X(X代表10) //前六位代表:省市县 // 接下来8位:出生年+月+日 //倒数第二位数字 奇数代表男 偶数代表女 var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/ //这样写不仅可以匹配,而且以后再捕获的时候,不仅可以把大正则匹配的结果捕获到,里面每一个小分组(小正则)匹配的结果也可以单独的捕获到:分组捕获 //分组捕获 reg.exec() // 年1950-2019 //第一段 1950-1999 //第二段 2000-2019 //第二阶段分成00-09 10-17 var reg = /^((19[5-9]\d)|(20((0\d)|(1[0-7))))$/
五:正则捕获
1、正则捕获:把当前字符串中符合正则的字符串捕获到
RegExp.prototype:exec 实现正则捕获的方法
捕获原理:当正则捕获的时候:①:先去验证当前字符串和正则是否匹配,如果不匹配,返回结果null
②:如果匹配,从字符串最左边开始,向右查找到匹配的内容,并且把匹配的内容返回
exec返回的结果分析
获取的结果是一个数组;数组中的第一项是当前本次大正则在字符串中匹配到的结果;里面有个index属性记录了当前本次捕获到结果的起始索引;input属性是当前正则操作的原始字符串
如果当前正则中有分组,获取的数组中,从第二项开始都是每个小分组,本次匹配到的结果(通过exec可以把分组中的内容捕获到)
注意:执行一次exec只能把符合正则规则条件中的一个内容捕获到,如果还有其它符合规则的,需要再次执行exec才有可能捕获到
2、正则捕获存在懒惰性
执行一次exec捕获到第一个符合规则的内容,第二次执行exec,捕获到的依然是第一个匹配的内容,后面匹配的内容不管执行多少次exec都无法捕获到
解决正则捕获的懒惰性:
在正则的末尾加修饰符g(全局匹配)
var str = '2018青青2019' var reg = /\d+/g console.log(reg.exec(str)[0]) //正则为什么会存在懒惰性 正则本身有一个属性:lastIndex (下一次正则在字符串中匹配查找的开始索引) 默认值:0,从字符串第一个字符开始查找匹配的内容 默认不管执行多少遍exec方法,正则的lastIndex值都不会变(也就是第二次以后查找的时候还是从第一个字符找,所以找到的结果永远都是第一个匹配的内容) 而且当我们手动把lastIndex进行修改的时候,不会起到任何作用 //为什么加了修饰符g就能解决懒惰性? 加了修饰符g,每一次exec结束后,浏览器默认会把lastIndex值进行修改,下一次从上一次结束的位置开始查找,所以可以得到后面匹配的内容
exec有自己的局限性:执行一次exec只能捕获到一个和正则匹配的结果(即使加了修饰符g),如果需要都捕获到,我们需要执行N次exec方法才可以
需求:手动编写一个方法,可以把当前正则匹配到的全部内容都捕获到
RegExp.prototype.myExecAll = function myExecAll(){ //this:当前需要处理的正则 =>/\d+/g //:str当前需要处理的字符串 var str = arguments[0] || '' var result =[] //首先判断this是否加了全局修饰符g,如果没有加,为了防止下面执行出现死循环,我们只让其执行一次exec即可,把执行一次的结果直接的返回 if(!this.global){ return this.exec(str) } var ary = this.exec(str) while(ary){ result.push(ary[0]) =》这部分是match的实现原理 每次只会把ary[0]匹配到,小分组内容会被忽略 ary = this.exec(str) //每次只会捕获到一个,所以这里要继续执行捕获下一个 } return result } var reg =/\d+/g var str = '2018青青2019' //: ["2018", "2019"]
3、使用字符串方法match实现捕获
var reg =/\d+/g var str = '2018青青2019' str.match(reg) //=>['2018','2019']
①:如果正则加了修饰符g,执行一次match会把所有正则匹配的内容捕获到
②:如果没有加修饰符g,执行一次match,只能把第一个匹配的结果捕获到
match的局限性:在加了修饰符g的情况下,执行match方法只能把大正则匹配的内容捕获到,对于小分组捕获的内容方法给其自动忽略了
//需求:把{n}整体捕获到,而且还要把括号中的数字也捕获到 var str = 'my name id {0},i am {1} years old~' var reg = /\{(\d+)\}/g str.match(reg) //=>["{0}","{1}"] //想要获取小分组的内容,只能是exec 参考上面myExecAll方法
4、使用test也可以实现正则的捕获
不管是正则的匹配还是正则的捕获,在处理时候的原理是没有区别的:从字符串中的第一个字符向后查找,找到符合正则规则的字符,如果可以找到,说明正则和字符串匹配(test检测返回true、exec捕获返回捕获的内容),如果知道末尾都没有匹配,说明正则和字符串不匹配(test返回false,exec返回null)
如果正则设置了修饰符g,不管使用test还是exec中的任何方法,都会修改lastIndex的值(下一次查找是基于上一次匹配结果的末尾开始查找的)
var reg = /\{(\d+)\}/g; var str = 'my name is {0}' if(reg.test(str)){ console.log(reg.lastIndex) //14 //test方法修改了lastIndex值 //不加g就可以,默认是从0开始 console.log(reg.exec(str)) //null }
var reg = /\{(\d+)\}/g; var str = 'my name is {0}' console.log(RegExp.$1) //0 $1:获取到当前本次匹配内容中第一个小分组捕获的内容 //test可以实现捕获,但是每一次只能获取到本次匹配结果中,第N个分组捕获的内容,$1第一个分组,$2第二个分组
六、replace(字符串中原有字符的替换)
str.replace(old,new) var str = '青青2017青青' str = str.replace('青青','青青alhh') //'青青alhh2017青青' str = str.replace('青青','青青alhh') //"青青alhhalhh2017青青"
在不使用正则的情况下,执行一次replace只能替换一个原有字符,第二次执行replace,还是从字符串的开始位置查找,把最新找到的字符替换为新字符(类似于正则捕获时候的懒惰性:每一次执行都是从字符串最开始的位置查找)
真实项目中,replace一般都是和正则搭配在一起使用的
var str = '青青2017青青' str = str.replace(/青青/g,'青青alhh') //"青青alhh2017青青alhh"
repalce原理:
1、当replace方法执行,第一项传递一个正则,正则不加g:把当前字符中第一个和正则匹配的结果捕获到,替换成新的字符
正则加g:把当前字符串中所有和正则匹配的内容都分别捕获到,而且每一次捕获,都会把当前捕获的内容替换为新字符
2、当replace方法执行,第二个参数传递的是一个函数(回调函数)
首先用正则到字符串中进行查找匹配,匹配到一个符合规则的,就把传递的函数执行一次
不仅执行这个函数,而且还把正则本次捕获的结果(和执行exec捕获的结果一样:数组、大正则匹配、小分组匹配都有)当做实参传递给这个函数(这样就可以在函数中获取这些值:而这些值就是正则每一次捕获的结果)
var str = 'my name is {0},i am {1} years old,i can {2} !' var reg = /\{(\d+)\}/g str.replace(reg,'alhh') //"my name id alhh,i am alhh years old,i can alhh !" str.replace(reg,function(){ console.log(arguments) //每一次匹配捕获到结果,不仅把这个方法执行了,而且还会把当前捕获的结果当做实参传递给这个函数(ARG) }) //第一次执行函数,获取的是ARG类数组 //0:'{0}' 本次大正则匹配的结果 //1:'0' 本次第一个小分组匹配的结果 //2:11 本次大正则匹配结果在字符串中的索引 index // 3:'my name...' 原始字符串 //和每一次执行exec实现捕获的结果非常类似 //return xxx =>每一次执行函数,函数中return的结果都相当于把本次大正则匹配的内容替换掉(原始字符串不变)
var str = '青青2018青青' str.replace(/青青/g,function(){ //console.log(arguments[0]) //当前本次大正则匹配的结果 return 'love' }) //"love2018love"