正则表达式从基础到深入实战

271 阅读12分钟

正则表达式从基础到深入实战

什么是正则?

正则就是一个规则,用来处理**字符串**的规则 1、正则匹配 编写一个规则,验证某个字符串是否符合这个规则,正则匹配使用的是 test 方法

2、正则捕获 编写一个规则,在一个字符串中把符合规则的内容都获取到,正则捕获使用的方法:正则的exec方法、字符串中的split、replace、match等方法都支持正则

var reg = /^$/; //=>两个斜杠中间包含一些内容就是正则,两个斜杠之间包含的全部内容都是元字符

正则的元字符和修饰符

任何一个正则都是由 元字符 和 修饰符 组成的

修饰符 g(global):全局匹配 i(ignoreCase):忽略大小写匹配 m(multiline):多行匹配

元字符 [量词元字符] +:让前面的元字符出现一到多次 ?:出现零到一次 *:出现零到多次 {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+/; //=>包含某某某即可,这里说明包含1到多个数字即可
var str = '正则匹配2018';
reg.test(str) =>true

reg=/^\d+/;
reg.test(str) =>false

reg=/^\d+$/;//=>只能是某某某的,这里说明只能是1到多个数字
reg.test('2017'); =>true
reg.test('2017正则2018'); =>false
reg.test('2'); =>true ^或者$只是一个修饰或者声明,不会占据字符串的位置

\

var reg = /^2.3$/;
reg.test('2.3'); =>true
reg.test('2+3'); =>true 点在正则中的意思:匹配除了\n以外的任意字符,而不是单纯的小数点

reg = /^2\.3$/;
reg.test('2.3'); =>true
reg.test('2+3'); =>false 使用转义字符把点转换为本身小数点的意思

x|y

var reg = /^18|19$/;//=>18 19 189 119 819 181 1819 ... 很多都符合这个规则
/*
 * 18或者19
 * 以1开头 以9结尾 中间是8或者1 
 * 以18开头或者以19结尾即可  =>'18珠峰' '珠峰19'...
 */

var reg = /^(18|19)$/;//=>此时只有18或者19符合我们的规则了

():正则中的分组,也可以理解为一个大正则中的一个正则(包起来的部分是一个整体);在正则中我们可以使用小括号改变一些默认的优先级

小分组还有第二个作用:分组引用 小分组的第三个作用:分组捕获

//=>分组引用:\1 或者 \2 ...出现和第N个分组一模一样的内容
var reg = /^([a-z])([a-z])\2([a-z])$/; //=> 符合的字符串:foot、book、week、attr、http...

[]

[xyz] [^xyz] [a-z] [^a-z]

//=>\w:数组字母下划线中的任意一个字符
var reg = /^[a-zA-Z0-9_]$/; //=>等价于\w

//=>中括号中出现的元字符,一般都代表本身的含义
var reg = /^[.?+&]+$/; //=>里面的四个元字符都是本身含义,例如:点就是小数点了,不是所谓的任意字符...

//=>需求:描述样式类名的规则(数字、字母、下划线、-),并且不能以-开头
//var reg = /^[\w-]+$/;
//var reg = /^[0-9a-zA-Z_-]+$/; //=>没有处理以-开头的情况
var reg = /^\w[\w-]*$/;
//=>需求:验证18~65之间的年龄
//var reg = /^[18-65]$/; //=>1或者8~6或者5中的任意一个字符,中括号中出现的18不是数字18,而是1或者8,当前正则是非法的:因为不能设置8~6这种范围

//=>分三个阶段处理:
// 18 或者 19   /(18|19)/
// 20 ~ 59     /([2-5]\d)/
// 60 ~ 65     /(6[0-5])/
var reg = /^((18|19)|([2-5]\d)|(6[0-5]))$/;

常用的正则表达式编写

验证是否为有效数字

/*
 * 可能是正数,可能是负数  12 -12
 * 整数或者小数 0 12 0.2 12.5 -12.3
 * 只要出现小数点,后面至少要跟一位数字
 * 小数点前面必须有数字
 */
var reg = /^-?(\d|([1-9]\d+))(\.\d+)?$/;
/*
 *  -? 负号可有可无
 *  (\d|([1-9]\d+))
 *     \d 一位数可以是任何值
 *     ([1-9]\d+) 多位数不能以零开头
 *  (\.\d+)? 小数部分可有可无,有的话点后面必须跟一位数字
 */

手机号码

/*
 * 11位数字
 * 1开头
 */
var reg = /^1\d{10}$/;

用户名:真实姓名

//=>/^[\u4E00-\u9FA5]$/ 中文汉字的正则
var reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10})?$/;

邮箱

var reg = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
/*
 * 以数字字母下划线开头
 * @前面可以是 数字、字母、下划线、-、. 这些符号
 * 不能把 -和. 连续出现,出现一次后面必须跟数字字母下划线
 *  
 * @后面的部分支持
 *   企业邮箱
 *   .com.cn 多域名情况
 */
// [A-Za-z0-9]+
// ((\.|-)[A-Za-z0-9]+)*
// \.[A-Za-z0-9]+
// @163.com.cn
// @chen-wan-jun.com.cn

身份证号码

/*
 * 18位
 * 前17位必须是数字
 * 最后一位可以是数字或者X(X代表数字10)
 *  
 * 371426198806064414
 *   前六位:省市县 371426
 *   接下来八位 出生年+月+日
 *   倒数第二位数字 奇数代表男 偶数代表女
 */
var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;
//=>这样写不仅可以匹配,而且以后捕获的时候,不仅可以把大正则匹配的结果捕获到,里面每一个小分组(小正则)匹配的结果也可以单独的捕获到 “分组捕获”

//=>年 1950~2017
//=>第一段 1950~1999
//=>第二段 2000~2017
//==> 00~09
//==> 10~17
//  /^((19[5-9]\d)|(20((0\d)|(1[0-7]))))$/

正则捕获

把当前字符串中符合正则的字符捕获到 RegExp.prototype:exec 实现正则捕获的方法

var str = '正则匹配2017扬帆起航2018';
var reg = /\d+/;

reg.exec(str);
/*
 * 当正则捕获的时候:
 * 1、先去验证当前字符串和正则是否匹配,如果不匹配返回的结果是null(没有捕获到任何的内容)
 * 2、如果匹配,从字符串最左边开始,向右查找到匹配的内容,并且把匹配的内容返回
 *  
 * exec捕获到结果的格式:
 * -> 获取的结果是一个数组
 * -> 数组中的第一项是当前本次大正则在字符串中匹配到的结果
 * -> index:记录了当前本次捕获到结果的起始索引
 * -> input:当前正则操作的原始字符串
 * -> 如果当前正则中有分组,获取的数组中,从第二项开始都是每个小分组,本次匹配到的结果(通过exec可以把分组中的内容捕获到)
 *  
 * 执行一次exec只能把符合正则规则条件中的一个内容捕获都,如果还有其它符合规则的,需要在次执行exec才有可能捕获到
 */

正则捕获存在懒惰性

执行一次exec捕获到第一个符合规则的内容,第二次执行exec,捕获到的依然是第一个匹配的内容,后面匹配的内容不管执行多少次exec都无法捕获到

解决正则捕获的懒惰性: 在正则的末尾加修饰符g(全局匹配)

//=>正则为什么会存在懒惰性?
/*
 * 正则本身有一个属性:lastIndex(下一次正则在字符串中匹配查找的开始索引)
 * 默认值:0,从字符串第一个字符开始查找匹配的内容
 * 默认不管指定多少遍exec方法,正则的lastIndex值都不会变(也就是第二次以后查找的时候还是从第一个字符找,所以找到的结果永远都是第一个匹配的内容)
 * 而且当我们手动把 lastIndex 进行修改的时候,不会起到任何的作用
*/

//=>为什么加修饰符g就解决了懒惰性?
/* 
 * 加了修饰符g,每一次exec结束后,浏览器默认会把lastIndex值进行修改,下一次从上一次结束的位置开始查找,所以可以得到后面匹配的内容了
 */

var reg = /\d+/g;
var str = '正则匹配2017杨帆起航2018';
console.log(reg.lastIndex);//=>0
console.log(reg.exec(str)[0]);//=>'2017'

console.log(reg.lastIndex);//=>8
console.log(reg.exec(str)[0]);//=>'2018'

console.log(reg.lastIndex);//=>16
console.log(reg.exec(str));//=>null

console.log(reg.lastIndex);//=>0
console.log(reg.exec(str)[0]);//=>'2017'

exec有自己的局限性:执行一次exec只能捕获到一个和正则匹配的结果(即使加了修饰符g),如果需要都捕获到,我们需要执行N次exec方法才可以

下面封装的myExecAll方法,目的是执行一次这个方法,可以把当前正则匹配到的全部内容都捕获到

RegExp.prototype.myExecAll = function myExecAll() {
    var str = arguments[0] || '',
        result = [];
    //=>首先判断THIS是否加了全局修饰符G,如果没有加,为了防止下面执行出现死循环,我们只让其执行一次EXEC即可,把执行一次的结果直接的返回
    if (!this.global) {
        return this.exec(str);
    }
    var ary = this.exec(str);
    while (ary) {
        result.push(ary[0]);
        ary = this.exec(str);
    }
    return result;
};

var reg = /\d+/g;
console.log(reg.myExecAll('正则2017匹配2018杨帆2019起航2020'));

使用字符串方法match实现捕获

var reg = /\d+/g;
var str = '正则2017匹配2018杨帆2019起航2020';
str.match(reg); //=>["2017", "2018", "2019", "2020"]

使用字符串match捕获: 1、如果正则加了修饰符g,执行一次match会把所有正则匹配的内容捕获到 2、如果没有加修饰符g,执行一次match只能把第一个匹配的结果捕获到

局限性: 在加了修饰符g的情况下,执行match方法只能把大正则匹配的内容捕获到,对于小分组捕获的内容方法给其自动忽略了

var str = 'my name is {0},i am {1} years old~,2017';
//=>需求:把{n}整体捕获到,而且还要把括号中的数字也获取到
var reg = /\{(\d+)\}/g;

// str.match(reg);//=>["{0}", "{1}"]
//=>想要获取小分组中的内容,我们只能使用EXEC处理了
function fn(reg,str){
	var ary=reg.exec(str),
		result=[];
	while(ary){
		result.push(ary);
		ary=reg.exec(str);
	}
	return result;	
}

使用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)) {
    //=>reg.test(str) : true
    console.log(reg.lastIndex);//=>14
    console.log(reg.exec(str));//=>null
}

var reg = /\{(\d+)\}/;
var str = 'my name is {0}~~';
if (reg.test(str)) {
    //=>reg.test(str) : true
    console.log(reg.lastIndex);//=>0
    console.log(reg.exec(str));//=>['{0}','0'...]
}

使用test不仅可以找到匹配的内容,也能像exec一样把找到的内容获取到 test返回结果是 true/false,所以靠返回结果肯定不行

var reg = /\{(\d+)\}/g;
var str = 'my name is {0}~~,i am {1} years old~~';
reg.test(str);//=>true
console.log(RegExp.$1);//=>0 获取到当前本次匹配内容中第一个小分组捕获的内容

reg.test(str);//=>true
console.log(RegExp.$1);//=>1 TEST可以实现捕获,但是每一次只能获取到当前本次匹配结果中,第N个分组捕获的内容 $1第一个分组 $2第二个分组 ...

所有支持正则的方法都可以实现正则的捕获(一般都是字符串方法)

字符串中常用的支持正则的方法: match split replace ...

var str = 'name=正则&age=8&lx=teacher';
str.split(/&|=/); //=>["name", "正则", "age", "8", "lx", "teacher"]

str.split(/(&|=)/); //=>["name", "=", "正则", "&", "age", "=", "8", "&", "lx", "=", "teacher"]

//=>在使用split进行字符串拆分的时候,如果正则中包含小分组,会把小分组中的内容都捕获到,放在最后的数组中

//=>本案例中的小括号仅仅是为了实现 改变默认的优先级 问题,但是我们不想把分组中的内容捕获到 => “只想匹配不想捕获”  我们可以使用 (?:)
str.split(/(?:&|=)/); //=>["name", "珠峰", "age", "8", "lx", "teacher"]

//=>只匹配不捕获:
//在当前一个分组中加了 ?: ,在正则检测匹配的时候,小分组可以起到自己应有的作用(例如:改变优先级...),但是在捕获的时候,遇到带?:的小分组,浏览器不会把当前这个分组中匹配的内容,单独去捕获了

var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{2})(\d)(\d|X)$/;
reg.exec(''); //=>["371426198806064414", "371426", "1988", "06", "06", "44", "1", "4"...]

var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(?:\d{2})(\d)(?:\d|X)$/;
reg.exec('371426198806064414');//=> ["371426198806064414", "371426", "1988", "06", "06", "4"...]

var reg = /^-?(\d|([1-9]\d+))(\.\d+)?$/;//=>计算是第几个分组的时候,我们从左向右找 ( 即可

replace

replace:字符串中原有字符的替换 str.replace(old,new)

var str = '正则2017正则2018';
str = str.replace('正则','正则匹配');
str = str.replace('正则','正则匹配');
//=>'正则匹配匹配2017正则2018' 没有实现我们希望的效果

//=>在不使用正则的情况下,执行一次replace只能替换一个原有字符,第二次执行replace,还是从字符串的开始位置查找,把最新找到的字符替换为新字符(类似于正则捕获时候的懒惰性:每一次执行都是从字符串最开始的位置查找)

真实项目中,replace一般都是和正则搭配在一起使用的

var str = '正则2017正则2018';
str = str.replace(/正则/g,'正则匹配');
//=>"正则匹配2017正则匹配2018"

replace原理: 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, function () {
    //=>传递的函数一共被执行三次
    //=>console.log(arguments) 每一次匹配捕获到结果,不仅把这个方法执行了,而且还会把当前捕获的结果当做实参传递给这个函数(ARG)
    /*
     * 第一次执行函数,获取的是ARG类数组
     *  0:'{0}' 本次大正则匹配的结果
     *  1:'0'   本次第一个小分组匹配的结果
     *  2:11    本次大正则匹配结果在字符串中的索引 index
     *  3:'my nam...' 原始字符串 input
     *
     * 和每一次执行exec实现捕获的结果非常类似
     */

    //return xxx;//=>每一次执行函数,函数中RETURN的结果,都相当于把本次大正则匹配的内容替换掉(原始字符串不变)
});