Javascript 正则表达式入门
作者: wuming 原文: https://segmentfault.com/a/1190000009324194
前言
在正文开始前,先说说正则表达式是什么,为什么要用正则表达式?正则表达式在我个人看来就是一个浏览器可以识别的规则,有了这个规则,浏览器就可以帮我们判断某些字符是否符合我们的要求。但是,我们为什么要使用正则表达式呢?下面我们就看一下下面这个业务场景。
验证QQ号的合法性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
*合法qq号规则:1、5-15位;2、全是数字;3、不以0开头
*/
//1.在不使用正则表达式的时候,我们可能会这样判断QQ号的合法性
var qq="6666666a6666";
if(qq.length>=5&&qq.length<=15&&!isNaN(qq)&&qq.charCodeAt(0)!=48){
alert("QQ合法");
}else{
alert("QQ不合法")
}
//2.使用正则表达式
var qq="066336";
var reg=/^[1-9][0-9]{4,14}$/;
if(reg.test(qq)){
alert("QQ合法");
}else{
alert("QQ不合法");
}
从上面这个例子可以看出来使用了正则表达式的时候,我们的代码量变少了,而且比较直观。如果遇到非常的复杂的匹配,正则表达式的优势就更加明显了。
使用方法
接着上面,我想先说说JS正则表达式是如何使用的。非常简单,只有两步而已。
第一步:定义一个正则表达式
定义正则表达式有两种方法,第一种通过 "/正则表达式/修饰符"
这种形式直接写出来,第二种通过 “new RegExp('正则表达式','修饰符)'”
创建一个 RegExp 对象。其中修饰符为可选项,有三个取值g
:全局匹配;i
:不区分大小写;m
:多行匹配
1
2
3
4
5
6
7
8
//第一种“/正则表达式/”
var reg1=/hello \w{3,12}/g;
//第二种new RegExp('正则表达式')
var reg2=new RegExp("hello \\w{3,12}",'g');
/**
*这里需要注意的是,第二种方法中由于字符串转义问题,"\\"代表"\"。
*/
上面这个定义方法,其实还有一个可选参数(修饰符)
,这里我们先不深入探究,后面我们再细说。
说到 RegExp 对象,下面要说一下 RegExp 对象自带的属性,并不复杂,这里我就列一下,不展开说了。
属性 | 描述 |
---|---|
global | RegExp 对象是否具有标志 g。 |
ignoreCase | RegExp 对象是否具有标志 i。 |
lastIndex | 一个整数,标示开始下一次匹配的字符位置。 |
multiline | RegExp 对象是否具有标志 m。 |
source | 正则表达式的源文本。 |
第二步:调用RegExp对象中的方法
RegExp 对象给我们提供了三种方法供我们使用,分别是test()
、exec()
和 compile()
。下面具体说一下这三个方法的用处。
1.test()
检索字符串中指定的值。返回 true 或 false。这个是我们平时最常用的方法。
1
2
3
var reg=/hello \w{3,12}/;
alert(reg.test('hello js'));//false
alert(reg.test('hello javascript'));//true
2.exec()
检索字符串中指定的值。匹配成功返回一个数组,匹配失败返回null
。
1
2
3
var reg=/hello/;
console.log(reg.exec('hellojs'));//['hello']
console.log(reg.exec('javascript'));//null
3.compile()
compile()
方法用于改变 RegExp。
compile()
既可以改变检索模式,也可以添加或删除第二个参数。
1
2
3
4
5
6
var reg=/hello/;
console.log(reg.exec('hellojs'));//['hello']
reg.compile('Hello');
console.log(reg.exec('hellojs'));//null
reg.compile('Hello','i');
console.log(reg.exec('hellojs'));//['hello']
如何写一个正则表达式
第一次接触正则表达式同学们,可能被这个正则表达式的规则弄得迷迷糊糊的,根本无从下手。小编我第一次学这个正则表达式的时候,也是稀里糊涂,什么元字符、量词完全不知道什么东西,云里雾里的。后面小编细细研究了一下,总结一套方法,希望可以帮助大家。
关于正则表达式书写规则,可查看 w3school,上面说的很清楚了,我就不贴出来了。我就阐述一下我写正则表达式的思路。
其实正则表达式都可以拆成一个或多个(取值范围+量词)这样的组合。针对每个组合我们根据JS正则表达式的规则翻译一遍,然后将每个组合重新拼接一下就好了。下面我们举个例子来试一下,看看这个方法行不行。
验证QQ号的合法性
合法qq号规则:1、5-15位;2、全是数字;3、不以0开头
第一步:拆成(取值范围+量词)这样的组合
根据QQ号的验证规则,我们可以拆成两个(取值范围+量词)的组合。分别是:
1
1.(1~9的数字,1个);2.(0~9的数字,4~14个)
第二步:根据正则表达式规则翻译(取值范围+量词)
1
2
1.(1~9的数字,1个) => [1-9]{1}或者[1-9]
2.(0~9的数字,4~14个) => [0-9]{4,14}
第三步:将翻译好的(取值范围+量词)组合进行拼接
初学者可能在拼接这一步会犯一个错误,可能会组合拼接成这个样子 /[1-9]{1}[0-9]{4,14}/
或者简写翻译成 /[1-9] [0-9]{4,14}/
这些都不对的。调用 test()
方法的时候,你会发现只要一段字符串中有符合正则表达式的字符串片段都会返回 true
,童鞋们可以试一下。
1
2
3
4
5
var reg=/[1-9][0-9]{4,14}/;
alert(reg.test('0589563'));
//true,虽然有0,但是'589563'片段符合
alert(reg.test('168876726736788999'));
//true,这个字符串长度超出15位,达到18位,但是有符合的字符串片段
正确的写法应该是这样的:
1
/^[1-9][0-9]{4,14}$/(用^和$指定起止位置)
下面我们看一个复杂点的例子:
验证国内电话号码
0555-6581752、021-86128488
第一步:拆成(取值范围+量词)这样的组合
这里会拆成两个大组合:
1
2
1、(数字0,1个)+(数字0~9,3个)+("-",1个)+(数字1~9,1个)+(数0~9,6个)
2、(数字0,1个)+(数字0~9,2个)+("-",1个)+(数字1~9,1个)+(数0~9,7个)
第二步:根据正则表达式规则翻译(取值范围+量词)
1
2
1、([0-0],{1})+([0-9],{3})+"-"+([1,9],{1})+([0,9],{6})
2、([0-0],{1})+([0-9],{2})+"-"+([1,9],{1})+([0,9],{7})
第三步:将翻译好的(取值范围+量词)组合进行拼接
这里我们先拼接一个大组合,然后再将大组合拼接起来
1
2
1、0[0-9]{3}-[1-9][0-9]{6}
2、0[0-9]{2}-[1-9][0-9]{7}
最后拼接为:
1
/(^0[0-9]{3}-[1-9][0-9]{6}$)|(^0[0-9]{2}-[1-9][0-9]{7}$)/
正则表达式拓展
除了 RegExp 对象提供方法之外,String 对象也提供了四个方法来使用正则表达式。
1.match()
在字符串内检索指定的值,匹配成功返回存放匹配结果的数组,否则返回 null
。这里需要注意的一点事,如果没有设置全局匹配g,返回的数组只存第一个成功匹配的值。
1
2
3
4
5
6
var reg1=/javascript/i;
var reg2=/javascript/ig;
console.log('hello Javascript Javascript Javascript'.match(reg1));
//['Javascript']
console.log('hello Javascript Javascript Javascript'.match(reg2));
//['Javascript','Javascript','Javascript']
2.search()
在字符串内检索指定的值,匹配成功返回第一个匹配成功的字符串片段开始的位置,否则返回 -1
。
1
2
var reg=/javascript/i;
console.log('hello Javascript Javascript Javascript'.search(reg));//6
3.replace()
替换与正则表达式匹配的子串,并返回替换后的字符串。在不设置全局匹配 g
的时候,只替换第一个匹配成功的字符串片段。
1
2
3
4
5
6
var reg1=/javascript/i;
var reg2=/javascript/ig;
console.log('hello Javascript Javascript Javascript'.replace(reg1,'js'));
//hello js Javascript Javascript
console.log('hello Javascript Javascript Javascript'.replace(reg2,'js'));
//hello js js js
4.split()
把一个字符串分割成字符串数组。
1
2
3
var reg=/1[2,3]8/;
console.log('hello128Javascript138Javascript178Javascript'.split(reg));
//['hello','Javascript','Javascript178Javascript']
结语
正则表达式并不难,懂了其中的套路之后,一切都变得简单了。在最后我想说点题外话,网上不乏一些文章记录一些常用的正则表达式,然后新手前端在使用正则表达式的时候都会直接拿来就用。在这里我想说一下自己的看法,这些所谓记录常用的正则表达式文章并非完全都是正确的,有不少都是错的。所以同学们在日后使用的过程尽量自己写正则表达式,实在不会了可以去参考一下,但真的不要照搬下来。咱不说这种会影响自己成长的话,咱就说你抄的一定都是对的吗?多思考一下,总没有坏处。
作者:duffy 原文:https://juejin.im/post/5acb4d3f6fb9a028c813295e
写在前面
正则的使用,相信大家工作中常用到的莫过于,表单验证:验证用户输入的内容是否符合我们设定的规则,例如:邮箱、电话、密码…,当然公司业务不同,手写能力强同学也可以用数据采集:在一堆数据中把我们需要的数据获取到,今天顺带总结一下,例如::
- URL传参(问号传参) 从列表页到详细页(或者其他的页面),我们点击不同的列表,在详细页面看到的内容也不同,但是详细页是同一个页面,想要展示不同的内容,需要把URL地址中的,?后面传递进来的参数获取到,通过传递过来的参数值的不一样展示不同的内容
- 信息采集:把一些数据转变为我们想要的格式,例如:”2018-4-3 12:14:00” ->”2016年04月03日”
今天顺带总结一下;
正则是什么
正则就是用来处理字符串的:匹配字符串的格式是否符合既定的格式(正则的匹配)、把一个字符串中符合既定格式的内容获取到(正则的捕获);test 和 exec 是正则里面的方法
test->匹配
1
2
3
4
var reg = /\d/;//-> \d一个0-9之间的数字 ->包含一个0-9之间的数字的规则
console.log(reg.test("2016"));//->true
console.log(reg.test("duff990fff"));//->true
console.log(reg.test("dufffff"));//->false
exec->捕获
1
2
3
4
5
var reg = /\d/g;
console.log(reg.exec("2016"));//->["2", index: 0, input: "2016"]
console.log(reg.exec("2016"));//->["0"...]
console.log(reg.exec("2016"));//->["1"...]
console.log(reg.exec("2016"));//->["6"...]
正则的组成
每个正则都是由元字符和修饰符两部分组成, “/”里面的 内容称为元字符 “/”[修饰符] g、i、m
1
2
3
g(global)->全局匹配
i(ignoreCase)->忽略大小写匹配
m(multiline)->换行匹配
正则常用的元字符
以下列出的都是常用的元字符,如果是不了解的童鞋需要,浪费你10分钟时间记一下,终身受益哈~
元字符:只要在//之间包含的所有的字符都是元字符
1) 具有特殊意义的元字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
\d -> 匹配一个0-9的数字,相当于[0-9],和它相反的是\D ->匹配一个除了0-9的任意字符
\w -> 匹配一个0-9、a-z、A-Z、_的数字或字符,相当于[0-9a-zA-Z_]
\s -> 匹配一个空白字符(空格、制表符...)
\b -> 匹配一个单词的边界
\t -> 匹配一个制表符
\n -> 匹配一个换行
. -> 匹配一个除了\n以外的任意字符
^ -> 以某一个元字符开头
$ -> 以某一个元字符结尾
\ -> 转义字符
x|y -> x或者y的一个
[xyz] -> x、y、z中的任意一个
[^xyz] -> 除了xyz中的任意一个字符
[a-z] -> 匹配a-z中的任意一个字符
[^a-z] -> 匹配除了a-z中的任意一个字符
() -> 正则中的分组
2) 代表出现次数的”量词元字符”
1
2
3
4
5
6
->+ : 出现一到多次
->* : 出现零到多次
->? : 出现零到一次
->{n} : 出现n次
->{n,} : 出现n到多次
->{n,m} : 出现n-m次
小试牛刀
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-> ^/$
var reg = /\d+/;//->包含一到多个数字(0-9)即可
console.log(reg.test("duffy2016"));//->true
reg = /^\d+$/;//->只能是一到多个数字
console.log(reg.test("duffy2016"));//->false
console.log(reg.test("2016"));//->true
-> .
var reg = /^2.6$/;
console.log(reg.test("2.6"));//->true
console.log(reg.test("2@6"));//->true
reg = /^2\.6$/;//->\是转义字符:把.这个特殊意义(任意字符)的元字符转变为只代表本身意义(小数点)的一个普通元字符
console.log(reg.test("2.6"));//->true
console.log(reg.test("2@6"));//->false
//->[]
//->在中括号中出现的所有字符(不管之前代表什么意思),在这里都是只代表本身的意思
//var reg = /^[2.3]$/;//->.这里只代表小数点,不是任意字符了
//reg = /^[\dz]$/;//->\d本身整体就是0-9之间的数字,在这里还是这个意思
//->在中括号中出现的两位数不是一个两位数,而是左边或者右边的
//var reg = /^[10-23]$/;//->1或者0-2或者3
在中括号中"-"具有连字符的作用,如果只想表示-,需要把其放在末尾
//var reg = /^[12-]$/;
//->中括号本身也有特殊的意思,如果需要只代表中括号本身的意思,需要进行转义
//var reg = /^\[\d+\]$/;//->"[200]"
//->x|y
//var reg = /^1|2$/;//->和这个有区别:/^[12]$/
//->1、2、12
//var reg = /^10|28$/;
//->10、28、1028、102、108、128、028 ->不是我们想要的那个10或者28了
()分组:把一个大正则划分成几个小正则
1) 改变正则的默认优先级
1
//var reg = /^(10|28)$/;//->10或者28
2) 分组的第二个作用:分组引用
1
2
"wood" "foot" "week" "feel" "door" "food" "good" "cool"...
var reg = /^[a-z]([a-z])\1[a-z]$/i;//->\1出现和第一个分组一模一样的内容
5、创建一个正则也有两种方式:字面量方式、实例创建的方式
1
2
3
4
5
6
7
8
//->实例创建第一参数值是字符串
//->想要和字面量方式保持统一的话,对于\d \w \n...这些都需要多加一个\,使其\d具有自己的特殊的意义
var reg = /^\d+$/ig;
console.log(reg.test("2016"));//->true
reg = new RegExp("^\d+$", "ig");
console.log(reg.test("2016"));//->false
reg = new RegExp("^\\d+$", "ig");
console.log(reg.test("2016"));//->true
1
2
3
4
5
6
7
8
9
10
11
12
13
//->对于[]、()这类的是没有区别的
// var reg = /^[0-9]$/;
// console.log(reg.test("0"));//->true
// reg = new RegExp("^[0-9]$");
// console.log(reg.test("0"));//->true
//->在实例创建的方式中,我们只要出现\,基本上都是要写\\的
// var reg = /^\[100\]$/;
// console.log(reg.test("[100]"));//->true
// reg = new RegExp("^\[100\]$");
// console.log(reg.test("[100]"));//->false
// reg = new RegExp("^\\[100\\]$");
// console.log(reg.test("[100]"));//->true
当一个正则表达式中需要把一个变量的值作为一个动态的规则:我们只能使用实例创建的方式
// var reg = /^duffy"+num+"peixun$/;//->
在//
之间包起来的都是元字符,有的是特殊的,有的就是代表本身意思的 ->以z开头,hufeng,出现一到多个”,nu,出现一个到一个m,”,peixun ->总之一句话:字面量方式中不存在什么字符串,也就没有所谓的字符串拼接,把变量的值拼接过来这一说了
1
2
3
4
5
// console.log(reg.test("duffy2016peixun"));//->false
// var num = 2016;
// var reg = new RegExp("^duffy" + num + "peixun$");
// console.log(reg.test("duffy2016peixun"));//->true
1
2
3
//字面量创建出来的是基本数据类型的值(不是严谨的实例,因为不能使用instanceof 检测是否是Number的实例),实例创建出来的是对象数据类型的值
// var num = 12;
// var num = new Number(12);
常用的正则表达式
1、手机号:11位数字、都是以1开头的
1
var reg = /^1\d{10}$/;
2、真实姓名(中国):两到四位的汉字
1
var reg = /^[\u4e00-\u9fa5]{2,4}$/;
3、验证邮箱
1
2
3
4
5
6
7
// 1633397595@qq.com
// 1633397595@163.com.cn
// 1633397595@163.com
// duffy_youxiang@tengxu.cn
//var reg7 = /^[\w.-]+@$/ //@分解两边 左边任意,\w 数字,大小写字符_ - . 右边
var reg7 = /^[\w.-]+@([1-9]|[a-z]|[A-Z])+(\.[A-Za-z]{2,4}){1,2}$/
console.log(reg7.test('1633397595@qq.com'))
4、验证有效数字的
1
2
3
4
5
0 -12 -12.3 -12.0 12.3 12.0
->可能出现"-"也可能不出现,出现的话只能出现一次
->整数部分是一到多个数字,但是两位数及以上的话不能以0开头
->小数部分可能有可能没有,一但有必须是 .后面跟一位或者多位数字
var reg = /^-?(\d|([1-9]\d+))(\.\d+)?$/;
5、年龄:18-65之间
1
2
3
4
18-19 /^1(8|9)$/
20-59 /^[2-5]\d$/
60-65 /^6[0-5]$/
var reg = /^((18|19)|([2-5]\d)|(6[0-5]))$/;
正则的捕获
正则的捕获分为两个阶段:
匹配:首先验证字符串和正则是否匹配,不匹配的话捕获到的结果为null
1
2
3
var str = "dafei";
var reg = /\d+/;
console.log(reg.exec(str));//->null
捕获:把正则匹配到的内容捕获到:捕获到的结果是一个数组,数组第一项是当前正则匹配捕获的内容,index:捕获的开始索引位置,input:捕获的原始字符串
每一次执行exec只能捕获到一个匹配的,想把所有匹配的都捕获到,至少要执行多次 ->但是一般情况下,我们不管执行多少次,每一次捕获的内容都是和第一次一模一样,后面的2017是捕获不到的”正则捕获的懒惰性”
1
2
3
4
var str = "duffy2016peixun2017";
var reg = /\d+/;
console.log(reg.exec(str));//->["2016", index: 7, input: "duffy2016peixun2017"]
console.log(reg.exec(str));//->["2016"...]
为啥会出现懒惰性? reg.lastIndex:正则每一次捕获的时候,在字符串中开始查找的索引, 正则每一次捕获结束后,默认的没有把lastIndex值进行修改,lastIndex一直是零,导致第二次捕获还是从字符串的起始位置开始查找,导致每一次捕获的都是第一个和正则匹配的
1
2
3
4
5
6
var str = "duffy2016peixun2017";
var reg = /\d+/;
console.log(reg.lastIndex);//->0 捕获的时候是从字符串开始的位置进行查找的
console.log(reg.exec(str));//->["2016"...]
console.log(reg.lastIndex);//->0 第二次捕获的话还是从字符串索引为零的位置开始查找
console.log(reg.exec(str));//->["2016"...]
有n个的匹配的就需要执行n次exec这个方法,比较的麻烦,生活如此美好,何必这么麻烦? ->字符串中提供了一个叫做match的方法,这个方法可以一次执行把所有匹配的捕获到
1
2
3
var str = "duffy2016peixun2017";
var reg = /\d+/g;//->不管用哪个方法,g是不能少的
console.log(str.match(reg));//->["2016", "2017"]
但是match也有自己的局限性? 如果正则中出现分组,而且需要执行多次exec才能全部捕获的,使用match不能把分组的内容捕获到.最佳解决方案 —replace,replace 天生为正则而生
实战
格式化时间字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var str = "2016-04-03";//->"2016年04月03日"
let reg = /^([1-9]\d{3})-(0?[1-9]|1[1-2])-([02]\d|[3][0-1])$/
console.log(reg.test(str))
str.replace(reg,function(){
console.log(arguments)
return arguments[1] + '年' + arguments[2] + '月' + + arguments[3] + '日'
})
function format(str) {
var reg = /^([1-9]\d{3})-(0?[1-9]|1[1-2])-([02]\d|[3][0-1])$/g
if (!reg.test(str)) return '输入日期格式不合法'
return (str.replace(reg,function(){
// console.log(arguments)
return arguments[1] + '年' + arguments[2] + '月' + + arguments[3] + '日'
}))
}
console.log(format('2018-12-30')) -> '2018年12月30日
数字大写转换
1
2
3
4
5
6
7
var str = "123678";//->"壹贰叁陆柒捌"
var ary = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"];
str = str.replace(/\d/g, function () {
//console.log(arguments[0]);//->每一次捕获到的内容(我们要的数字)
return ary[arguments[0]];
});
console.log(str);
简易模板引擎实现的原理
1
2
3
4
5
6
7
8
var data = ['duffy', '27', 'china', 'javascript']
var str = "my name is {0},my age is {1},i com from {2},i can do {3}~~";
var reg = /\{(\d)\}/g
console.log(reg.test(str))
str = str.replace(reg, function(){
return data[arguments[1]]
})