在最近一次前端面试题中,有一个面试题:"一个字符串数字,每隔3位添加一个空格"
// 从左到右每隔三位用空格分隔:'123 456 676 89'
var str = '12345667689';
方法一(不会正则的):
var str = '12345454645'
//添加分隔符函数:str-字符串,num-位数,sep-分隔符
function addDelimiter(str,num,sep){
var arr = []
while(str.length>num){
arr.push(str.slice(0,num))
str = str.slice(num)
}
if(str.length>0) arr.push(str)
return arr.join(sep)
}
addDelimiter(str,3,' ')
方法二(会正则的):
str.replace(/(\d{3})\d/g,'$1 ')
对比上面的代码,虽然都实现了功能,但是方法二显示高大上一些:代码短、快捷、高端。那么方法二中的'$1'是什么,'\d'又是什么鬼,为啥十来个字符就解决了这个问题。这正是正则的强大之处。下面我们来正式学习一下正则。
一、正则介绍
正则表达式(Regular Expression)是一种文本模式,它使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
简单的来讲就是:按照某种规则去匹配符合条件的字符串。它的强大之处就在于字符串的匹配,下面直接上两个栗子:
1、判断邮箱是否合法:
var email = '1938129057@qq.com'
var reg_email = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/
reg_email.test(email) // true说明email符合reg_email格式
2、把英文语句中的'is'都替换成大写的'IS'
var str = 'This is a test'
var reg = /\bis\b/g
str.replace(reg,'IS') // "This IS a test"
这时候,你觉得看不懂这里的reg是什么鬼,没关系,给大家推荐一个图形化的网站:https://regexper.com/。它可以把正则表达式转换成一种易懂的图形化语言:比如上面的reg_email转化后
二、RegExp对象
前面给了几个关于正则表达式使用的例子,那么我们怎么在JavaScript中创建一个正则表达式呢。
1、内置对象RegExp对象:通过new创建
// 语法
var patt=new RegExp(pattern,modifiers);
2、字面量方式:两个斜线之间,后面加修饰符
// 语法
var patt=/pattern/modifiers;
-
pattern :由元字符组成的字符串规则;
-
modifiers:规则作用时的修饰符,是否全局匹配 g,是否区分大小写 i,是否多行匹配 m;
下面我们用两种方式分别创建正则表达式:
// 把字符串中所有的'abc'子串替换成'123'
var str = 'fsabcdffgfdfhfhabc'
var reg1 = new RegExp('abc','g');
var reg2 = /abc/g;
str.replace(reg1,'123');
//结果:"fs123dffgfdfhfh123"
str.replace(reg2,'123');
//结果:"fs123dffgfdfhfh123"
我们会发现结果是一致,但是字面量的方式更便捷一些,所以推荐使用字面量的方式。
三、元字符
1、原义文本字符
-
单个的字母:a-z、A-Z其中任意个字母,表示它本身;
-
单个的数字:0-9,其中任意一个数字,表示它本身;
-
其他字符:_(下划线)、-(短横线)等,表示它本身;
注:愿意字符出现在正则中是,就是去匹配它本身,如上面的示例。
2、元字符:有特殊含义的非字母字符
-
\b:单词边界,前面已经使用过了
-
\B:非单词边界
-
$:以xxx结尾
-
^:以xxx开始
// 单词边界:匹配单词is,而不会匹配到this中的is
var reg = /\bis\b/g
'This is a test'.replace(reg,'123') // "this 123 a test"
// ^:匹配以abc开头,而不会匹配到后面的abc子串
// $:匹配以abc结尾,而不会匹配到前面的000子串
var reg2 = /^abc/g
var reg3 = /000$/g
'abc123000abc000'.replace(reg2,'QQQ') //"QQQ123000abc000"
'abc123000abc000'.replace(reg3,'XXX') //"abc123000abcXXX"
-
\t:水平制表符
-
\n:换行
-
\r:回车
-
\0:空字符
-
[]:范围符号
-
[a-z]:匹配26个小写字母中的任意一个
-
[A-Z]:匹配26个大写字母中的任意一个
注意:[]中的'-'只是用来指示范围,不是原义文本字符
注意:[]中的'^'表示非,使用时请注意
// 把字符串中的数字替换成‘-’
var reg = /[0-9]/g; //也可以写成:/\d/g
'a1d2c3v4b9'.replace(reg,'-'); //"a-d-c-v-b-"
// 把字符串中的非数字替换成‘-’
var reg1 = /[^0-9]/g;
'a1d2c3v4b9'.replace(reg1,'-'); //"-1-2-3-4-9"
-
.:除了回车换行之外的所有字符 [^\r\n]
-
\d:匹配数字字符0-9任意一个
-
\D:匹配除0-9之外的任意一个[^0-9]
-
\w:单词字符(字母、数字、下划线) [a-zA-Z_0-9]
-
\W:非单词字符
-
\s:空白字符
-
\S:非空白字
3、量词
-
?:最多出现1次,0或1次
-
+ :至少1次
-
* :出现任意次
-
{n} :出现n次
-
{n,m}: 出现n到m次
-
{n,} :至少出现n次
// 数字连续出现5次,则替换成‘**’
var reg = /\d{5}/g
'a1234b45678v9875464'.replace(reg,'**') //"a1234b**v**64"
4、分组()
匹配字符串name连续出现三次的场景,到目前为止我们可能有以下几种方式实现:
// 匹配name连续出现三次,并替换成一个name
// 没学习量词之前
var reg = /namenamename/g;
'namenamenameee'.replace(reg,'name') //"nameee"
// 学了量词后
var reg2 = /name{3}/g; //好像是那么回事后,
'namenamenameee'.replace(reg2,'name') //"namenamename"
测试后发现不是那么回事,怎么是匹配成了"nameee",e字母出现了三次。因为量词仅作用到紧挨着它的那个元素,而不是整个单词,所以这里需要把name看做一个整体。
// 匹配name连续出现三次,并替换成一个name
var reg2 = /(name){3}/g; //好像是那么回事后,
'namenamenameee'.replace(reg2,'name') //"namenamename"
5、分组与反向引用
需求:2015-12-25 =》12/25/2015。
瞬间脑仁疼,好吧JavaScript分割再组装吧,也可以实现,但是太繁琐了。这里就要用到“反向应用”了.
反向应用:捕获分组内的内容,从左到右,从内到外,依次命名为$1……$n。
var reg = /(\d{4})-(\d{2})-(\d{2})/
'2015-12-25'.replace(reg,'$2/$3/$1') //"12/25/2015"
顺便我们再来看看这个正则图像化后的示意图:
上面的#1、#2、#3就对应$n.
当我们进行了分组,又不想被反向引用捕获到,怎么办呢?在分组的前面加上'?:',例如:
var reg = /(?:name){3}-(\d){2}-(ok)/
四、贪婪模式与非贪婪模式
使用量词{m,n}时,正则是怎么匹配的呢,是匹配m次还是n次呢。正则默认是尽可能多的匹配,也就是所谓的贪婪模式:
var reg = /\d{3,6}/g
'12345678'.replace(reg,'X'); //"X78"
'123456789'.replace(reg,'X'); //"XX"
如何解除贪婪模式呢:在量词的后面添加'?'
var reg = /\d{3,6}?/g
'12345678'.replace(reg,'X'); //"XX78"
'123456789'.replace(reg,'X'); //"XXX"