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

作者:日期:2017-11-23 16:48:50 点击:123

什么是正则?

正则就是一个规则,用来处理字符串的规则
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 = '珠峰2017培训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
// @zhu-feng-pei-xun.com.cn

身份证号码

/*
* 18位
* 前17位必须是数字
* 最后一位可以是数字或者X(X代表数字10)
*
* 130828199012040617
* 前六位:省市县 130828
* 接下来八位 出生年+月+日
* 倒数第二位数字 奇数代表男 偶数代表女
*/
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('130828199012040617'); //=>["130828199012040617", "130828", "1990", "12", "04", "06", "1", "7"...]
 
var reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(?:\d{2})(\d)(?:\d|X)$/;
reg.exec('130828199012040617');//=> ["130828199012040617", "130828", "1990", "12", "04", "1"...]
 
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的结果,都相当于把本次大正则匹配的内容替换掉(原始字符串不变)
});

上一篇: JAVASCRIPT中的定时器及动画

下一篇: JS中的数据类型检测专题汇总