一、什么是正则?

正则就是一个规则,用来处理 字符串 的规则

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"