# 14、正则
TIP
正则就是一个规则,用来处理字符串的规则
1、正则匹配
编写一个规则,验证某个字符串是否符合这个规则,正则匹配使用的是test
方法
2、正则捕获
编写一个规则,在一个字符串中把符合规则的内容都获取到;正则的exec
、字符串的split、replace、match
等方法都支持正则
# 正则的创建方式
TIP
1.字面量方式:var reg=/\d/
;
2.实例创建方式 var reg=new RegExp('a','b')
;支持两个参数,第一个为元字符,第二个是修饰符
# 正则两种创建方式的区别
区别
1,字面量方式中出现一切字符都会按找元字符来解析,所以不能进行变量值的拼接,而实例创建的方式是可以的。
2,字面量方式中直接写\d
就可以,而在实例创建方式中需要把它转义;只需要在\d
之前再加一个\即可。即\\d
# 正则的元字符和修饰符
任何一个正则都是由
元字符
和修饰符
组成的
# 修饰符
ignoreCase(i):忽略大小写;
global(g):全局匹配;找到所有的匹配而不是只找到第一个匹配
multiline(m):多行匹配;
# 元字符
代表出现次数的量词元字符
* : 出现零到多次
? : 出现0次或1次
+ : 出现1到多次
{n} : 出现n次
{n,} : 出现n到多次
{n,m} : 出现n到m次
具有特殊意义的元字符
\ : 转义字符(两个\\就表示两个\\)
^ : 以某一个元字符开始,不占位置。(只对字符串的开头起作用)
$ : 以某一个元字符结尾,不占位置。(只对字符串的末尾起作用)
. : 除了\n(换行符)以外的任意字符
() : 分组
x|y : x或y中的一个
[xyz] : x或者y或者z中的一个
[^xyz] : 除了x y z的任何一个字符
[a-z] : a-z之间的任何一个字符
[^a-z] : 除了a-z的任何一个字符
\d : 一个0-9之间的数字
\D : 匹配一个非数字字符
\b : 一个边界符:“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”,单词左右两边是边界,-的左右两边也是边界,所以会把zhu-mu-la-ma-feng当作5个单词,而我们想把它当成一个单词
\n : 匹配一个换行符。
\s : 匹配一个空白字符 (空格,制表符,换页符)
\w : 数字,字母,下划线中的任意一个字符
?: : 只匹配不捕获
?= : 先行断言 x(?=y) 当且仅当'x'后面跟着'y'时,匹配'x'。
?! : 正向否定查找 x(?!y) 当且仅当'x'后面不是'y'时,匹配'x'
?<= : 后行断言 (?<=y)x 当且仅当'x'前面跟着'y'时,匹配'x'
?<! : 反向否定查找 (?<!y)x 当且仅当'x'前面不是'y'时,匹配'x'
# 正则中[]
的特殊含义
中括号
1、
[]
里面出现的元字符一般都是代表本身意义的,比如[xyz]
(表示x
或y
或z
其中的一个),但是有一些依然表示符号本身的意思如[\d\w]
(表示数字或字母其中的一个)2、中括号中的横线(
-
)代表两种意思- 出现在中间:
[a-z0-9]
:-
代表范围连接符 - 出现在末尾:
[a-z_0-9-]
:如果-
的左右两边任何一边没有内容那么-
代表本身的意思,即匹配一个横线。
- 出现在中间:
3、
[]
中出现的两位数不代表本身意思,而是以一位数来解析,如[18]代表 1或8 两个数字中的一个;
中括号中需要转义的字符:
- 1、反斜杠(
\
)必须转义 - 2、方括号(
[
,]
)必须转义 - 3、
^
在 首位 必须转义,-
在 中间 必须转义
# 正则中()的作用
提高优先级
正则中的分组,也可以理解为一个大正则中的一个正则(包起来的部分是个一个整体);我们可以使用小括号改变一些默认的优先级。如
(x|y)
分组引用
(\d)\1
:\1
代表引用和前面相同的数字;
分组捕获
正则捕获的时候不仅可以把大正则匹配的内容捕获到,而且也会把小括号中的内容捕获到
# 常用的正则表达式编写
TIP
注意:正则中如果同时加入了^
和 $
表示只能是xxx,不加的话表示包含xxx,而且^
只对字符串的开头起作用,$
只对字符串的结尾起作用;
reg = /^-?((\d)|([1-9]\d+))(\.\d+)?$/ 有效数字正则
reg = /^1\d{10}$/ 手机号验证
//=>/^[\u4E00-\u9FA5]$/ 中文汉字的正则
// 姓名的正则:
reg = /^[\u4E00-\u9FA5]{2,10}$/;
reg = /^[\u4E00-\u9FA5]{2,10}(·[\u4E00-\u9FA5]{2,10})?$/;
// 邮箱正则
/*
* 以数字字母下划线开头
* @前面可以是 数字、字母、下划线、-、. 这些符号
* 不能把 -和. 连续出现,出现一次后面必须跟数字字母下划线
*
* @后面的部分支持
* 企业邮箱
* .com.cn 多域名情况
*/
reg=/^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/
身份证:
前六位:省市县
接下来八位 出生年+月+日
接下来的两位:没什么用
倒数第二位数字 奇数为男,偶数为女
reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|X)$/;
/* web URL 解析正则 */
const reg = /^(https?:\/\/)([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})(:\d+)?([a-zA-Z0-9\/_-]+\/[a-zA-Z0-9_-]*)?(\?(=?&?[a-zA-Z0-9_-]*)*)?$/i
# web URL解析
使用正则
缺点:不会对URL进行解码
const str = 'https://carbon.now.sh/my/collection/?bg=rgba(100,180,250,1)&wc=true&wa=true&code=const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B#qq#'
const urlParse = function (url) {
const reg = /^(?:(https?:)\/\/)?([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})(?::(\d+))?([a-zA-Z0-9\/_-]*)?\/?(([?&][^?#=&]+=[^?#=&]*)*)?(#.*)?$/i
/*
协议($1):(https?:)
域名($2):([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})
端口号($3):(:\d+)
路径($4):([a-zA-Z0-9\/_-]*)
参数($5):(([?&][^?#=&]+=[^?#=&]*)*)
哈希($7):(#.*)
*/
reg.test(str)
const { $1, $2, $3, $4, $5, $7 } = RegExp
const res = {
protocol: $1 || 'https:',
hostname: $2,
port: $3,
origin: `${$1}//${$2}:${$3}`,
host: `${$2}:${$3}`,
pathname: $4,
search: $5,
hash: $7,
query: {}
}
if ($5) {
$5.replace(/[?&]([^?=&]+)(?:=([^?=&]*))?/g, function (k, group1, group2) {
res.query[group1] = group2
})
}
return res
}
const res = urlParse(str)
/*
console.log(res)
{
"protocol": "https:",
"hostname": "carbon.now.sh",
"port": "",
"origin": "https://carbon.now.sh:",
"host": "carbon.now.sh:",
"pathname": "/my/collection/",
"search": "?bg=rgba(100,180,250,1)&wc=true&wa=true&code=const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B",
"hash": "#qq#",
"query": {
"bg": "rgba(100,180,250,1)",
"wc": "true",
"wa": "true",
"code": "const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B"
}
}
*/
使用原生URL对象
const urlParse = function (url) {
const url = new URL(str)
if (url.search) {
url.query = {}
url.searchParams.forEach((value, key) => url.query[key] = value)
}
return res
}
const res = urlParse(str)
/*
console.log(res)
{
href: 'https://carbon.now.sh/my/collection/?bg=rgba(100,180,250,1)&wc=true&wa=true&code=const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B#qq#',
origin: 'https://carbon.now.sh',
protocol: 'https:',
username: '',
password: '',
host: 'carbon.now.sh',
hostname: 'carbon.now.sh',
port: '',
query:{
bg: 'rgba(100,180,250,1)',
code: 'const vpc = Vpc.fromLookup(this, 'MyExistingVPC', { isDefault: true });\nnew cdk.CfnOutput(this, \"MyVpc\", {value: vpc.vpcId });',
wa: 'true',
wc: 'true'
}
pathname: '/my/collection/',
search: '?bg=rgba(100,180,250,1)&wc=true&wa=true&code=const%20vpc%20%3D%20Vpc.fromLookup(this%2C%20%27MyExistingVPC%27%2C%20%7B%20isDefault%3A%20true%20%7D)%3B%0Anew%20cdk.CfnOutput(this%2C%20%22MyVpc%22%2C%20%7Bvalue%3A%20vpc.vpcId%20%7D)%3B',
searchParams: URLSearchParams {},
hash: '#qq#'
}
*/
# 正则的捕获
# exec
正则的捕获:reg.exec(str)
捕获的内容是一个数组,数组中的第一项是当前正则捕获的内容,如果有小括号(),则第二项捕获的是小括号里的内容。
- index:捕获内容在字符串中开始的索引位置。
- input:捕获的原始字符串。如果没有匹配的返回null;
[懒惰性]
捕获的特点:执行一次捕获一次,每次执行exec
只捕获第一个匹配的内容。
为什么会存在懒惰性?
- 每个正则都自带
lastIndex
属性:正则每一次捕获在字符串中开始查找的位置,默认值为0; - 我们手动修改
lastIndex
的值也不会影响exec
每次开始查找的索引。
如何解决只捕获第一个匹配(懒惰性)?
在正则末尾加一个修饰符g
原理:
修改lastIndex
的值为上一次捕获内容之后的第一个字符的索引;加了g
之后能保证下次捕获第二个匹配的内容;
通过加g 每次捕获结束后,lastIndex
的值都会改变成最新的值,下次捕获从最新的位置开始找。如果没有找到符合的,则返回null
;
如何解决exec
执行一次捕获一次?
// 我的方法
RegExp.prototype.myExec=function(){
var str=arguments[0]||'',ary=this.exec(str),ary1=[];
if(this.global!==true)return ary;
while(ary){
ary1=ary1.concat(ary);
ary=this.exec(str);
}
return ary1;
}
// 别人的方法
RegExp.prototype.myExec=function () {
var str=arguments[0]||'', ary=this.exec(str),result=[];
if(!this.global)return ary;
while (ary){
result.push(ary);
ary=this.exec(str);
}
return result;
}
# match
用字符串方法实现多次捕获
str.match(reg)
1、如果正则中加了修饰符g,那么执行一次就可以把所有匹配的结果捕获到
2、如果不加g,那么只捕获第一次匹配的结果。返回一个数组,数组中是捕获的每一项;
[局限性]
在分组捕获中,如果正则不加修饰符g
,
- 那么
match
和exec
一样,可以捕获到小分组的内容。
如果正则加了修饰符g
match
只能捕获到大正则匹配的内容,而对于小分组匹配的内容是无法捕获到的;
注意:由于test
和exec
的原理相同,如果加了g
,都是修改lastIndex
值来匹配或者捕获,所以使用test
和exec
都会修改lastIndex
的值,所以使用exec
捕获之前最好不要使用test
;
用test
也可以捕获符合的字符串
reg.test(str);
RegExp.$1// 只能捕获第1个小分组里面的内容
RegExp.$2// 只能捕获第2个小分组里面的内容
RegExp.$3// 只能捕获第2个小分组里面的内容
同reg.exec(str);// 也能使用RegExp.$1来捕获第1个小分组
RegExp.$1// 只能捕获第1个小分组里面的内容
RegExp.$2// 只能捕获第2个小分组里面的内容
# 所有的支持正则的方法都可以实现正则的捕获(一般都是字符串的方法)
字符串中常用的支持正则的方法:
match
replace
split
:如果给的正则中包含小分组,那么会把小分组中的内容也捕获到;
?:只匹配不捕获, 只能放在小分组的开头。如果加在分组中,那么只会匹配该分组中的内容,而不会捕获
/(?:&|=)/
计算是第几个分组的时候,我们从左向右找(
即可
var reg = /^-?(\d|([1-9]\d+))(\.\d+)?$/;//=>计算是第几个分组的时候,我们从左向右找 ( 即可
# replace(第二个参数支持直接在字符串中使用$1-9)
替换字符串
在不使用正则的情况下,每次执行只能替换第一个匹配的字符串,而且每次执行都是从字符串索引为0的位置开始查找
第一个参数为正则时,正则捕获几次,就替换几次,第二个参数是函数时,正则捕获几次,函数就执行几次,函数中返回的是什么,就相当于把正则捕获的内容替换成什么。
第二个参数为函数时,正则捕获几次,函数就执行几次,函数执行的时候还会默认的传入三个参数:
context
(捕获的内容)
index
(捕获内容在字符串中的开始索引)
input
(原始的字符串)
str.replace(/珠峰/g,function(context,index,input){
// arguments[0]大正则匹配的内容
//如果正则中有分组的话
// arguments[1]第1个小分组匹配的内容
// arguments[2]第2个小分组匹配的内容
// …………直到小分组的内容显示完
// argument[n]每一次捕获的开始索引
// argument[n+1]原始字符串
return // 的是什么就会把正则每次捕获的内容替换成什么
})
// 需求:把每个单词的首字母替换为大写,wo-de-shi-jie 当成一个单词
var str='my name is wo-de-shi-jie,i am 8 years old,i am the world No1!';
var reg=/[a-zA-Z-]+/g;
str=str.replace(reg,function () {
return arguments[0].slice(0,1).toUpperCase() + arguments[0].slice(1);
})
console.log(str);
# 时间格式化字符串
var str = '2017-11-07 16:37:00';
//=>'2017年11月07日 16时37分00秒'
//=>使用正则实现
//1、执行一次捕获操作,得到需要的六个结果
var reg = /^(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})$/g;
// str = str.replace(reg, function () {
// var arg = arguments;
// return arg[1] + '年' + arg[2] + '月' + arg[3] + '日 ' + arg[4] + '时' + arg[5] + '分' + arg[6] + '秒';
// });
str = str.replace(reg, '$1年$2月$3日 $4时$5分$6秒');//=>$1等价于第一个分组中获取的内容,类似于上面代码的arg[1]
console.log(str);//=>'2017年11月07日 16时37分00秒'
String.prototype.myformate = function () {
var ary=this.match(/\d+/g);
var temp=arguments[0]||'{0}年{1}月{2}日 {3}时{4}分{5}秒';
var reg=/\{(\d)\}/g;
temp=temp.replace(reg,function () {
var value=ary[arguments[1]]||'0';
return value.length<2?'0'+value:value;
})
return temp;
}