JavaScript中的正则表达式
使用场景
没有工作之前很不注重正则表达式这一块的知识,真正接触了公司项目,发现用到正则表达式的地方还是很多的,具体有发现以下使用场景:
1.一个用户注册账户可能要提供电话号码,身份证号码,这些我们就要验证这些信息符不符合格式,包括用户设置密码,用户名等等,我们都不愿意用户想一些乱七八糟的名字,这样就需要有所限制;
2.我们要提取一个url里面的一些参数用来作其他的url请求,但是url里那么多字符,我们就需要找到我们需要的;包括接口返回的时间可能不是我们想要的格式,那我们也得提取有用的信息,过滤无用的字符;
基本知识
在我的理解,JavaScript中的正则表达式就是用一个通用的表达式来表示一类具有某种相同特征的字符串。所以我们就需要看看有哪些通用的符号。
1.认识通用字符(字符类)
1.我们先来看最基本的,表示数字,字符串和空格的那些:
\d : digit 数字 *注意:只能匹配一位数字
\D :反义 任意非数字的字符
\s :space 任意空白符
\S :反义 任意非空白符
\w : word 任意ASCII字符组成的单词 *注意:只能匹配一个字符
\W :反义 任意不是ASCII字符组成的单词
这个时候,我们就可以利用这几个通用字符来匹配一些字符串了,那么问题来了,在JavaScript中,正则表达式怎么用呢?下面是定义一个正则表达式的方法:
//推荐写法
var pattern = /s$/; //两边加上斜杠来告诉编译器这是个正则表达式
//不推荐写法
var pattern = new RegExp("s$");//也可以用构造函数来创建
接下来的任务就是检测一个字符串
到底和我们所定义的正则表达式
是不是相匹配的:
String对象支持4种使用正则表达式的方法:
search(); 接受一个正则表达式参数,返回第一个与之匹配的子串的起始位置
replace(); 进行替换操作。接受两个参数,第一个是正则表达式,匹配第一个子串来进行替换,第二个参数是指定替换后的字符
match(); 接受一个正则表达式参数,返回一个由匹配结果组成的数组
split(); 用来将字符串分割成数组,唯一的参数就是分割标准,可以是字符串,也可以是正则表达式
先应用一下第一个:
var pattern1 = /\w/;
var text1 = 'javascript';
text1.repalce(pattern1,'JavaScript') //=>JavaScriptavascript
var pattern2 = /\d/;
var text2 = '123456';
text2.repalce(pattern2,'654321') //=>65432123456
从输出结果上我们就能看出来,很多教材上的表述就很令人无语,明明只能匹配一个数字和一个字符,为什么要说成单词???
2.现在问题又来了,有时候我们的匹配要求比这个要细致一点或者更宽泛一点:
细致:比如我们只想匹配字符里的a或者b或者c,这个时候就不再是简单的\w能满足了,这个时候一个很重要的字符 ‘[ ]’就能解决问题,/[abc]/就表示可以匹配’a’或者’b’或者’c’;
宽泛:’.’这个符号可以用来表示除换行符和中指符之外的任意字符。
[...] : 方括号内任意字符 [a-z]匹配任意小写字母
[^...] : 不在方括号内的任意字符 [^a-z]匹配不是小写字母的任意字母,数字,下划线
. : 除换行符和中指符之外的任意字符
[\b] : 退格直接量?
2.认识重复匹配(尽可能多与尽可能少:贪婪与懒惰)
//贪婪模式
* : 匹配前一项重复零次或更多次 /\d*/
+ : 匹配前一项重复一次或更多次 /\d+/
? : 匹配前一项重复零次或一次 /\d?/
{n} : 匹配前一项重复n次 /\w{3}\d/ //匹配3个字符和一个数字
{n,} : 匹配前一项重复n次或更多次 /\d{2,}/
{n,m} : 匹配前一项重复n到m次,即n到m次之间任意多次都进行匹配,例如\d{1,3}可以匹配999以内的任意数字 /\d{2,4}/
上述重复匹配会在满足条件的情况下尽可能多的去匹配,比如/a.*b/表示匹配以a为开头,b为结尾的字符串,那么对于字符串’aabab’,就会匹配一整个字符串。
而懒惰模式下呢?就是在上述基础上后面再加一个问号?,
//懒惰模式
*? : 重复任意次,但尽可能少重复
+? : 重复1次或更多次,但尽可能少重复
?? : 重复0次或1次,但尽可能少重复
{n,m}? : 重复n到m次,但尽可能少重复
{n,}? : 重复n次以上,但尽可能少重复
/a.*?b/就会匹配’aab’这个子串。
3.修饰符
还记得/a.*?b/吗?对于字符串’aabab’,只能匹配’aab’,如果我们想要他匹配所有符合的,即’aab’和’ab’,就需要用到修饰符了:
i : 执行不区分大小写的匹配
g : 执行全局匹配,即找到所有匹配,而不是找到第一个就停止
m : 多行匹配,^匹配一行的开头和字符串的开头,$匹配行的结束和字符串的结束
所以就可以通过/a.*?b/g来实现全局匹配,可以通过//gi来使用多个修饰。
4.分支,分组与引用
分支条件即可以使用分隔符|起到或的作用,即多个表达式通过|连接在一起,满足任意一个表达式即进行匹配。如通过(0\d{2}[)]\d{8}|\d{3}-\d{8}可以同时匹配到以下两种电话号码010-12345678和(010)12345678
注意分支条件的匹配是有顺序的,即从左到右测试,如果测试成功,则停止测试,不再进行后续的测试,所以使用分支条件连接多个表达式时应将限制条件较多的表达式放在前面。eg: \d{5}-\d{4}|\d{5}与\d{5}|\d{5}-\d{4}的匹配结果是不一样的,明显前一个的较精确,因为它的将限制较多的表达式放在了前面,而后一个在匹配时会忽略掉后一个分支限制。
分组即通过()将多个匹配作为一组,然后对这整个组进行操作。例如将匹配成功的某个需要字串进行重复、再匹配等。
同时,被圆括号括起来的子表达式也可以被通过’\数字’的方式进行引用,数字代表第几个括号子串的引用:
/['"][^'"]['"]/ <=> /(['"])[^'"]/1/
这里我们又得到几个字符:
| : 选择符(分支),匹配该字符左边的子表达式或右边的子表达式
(...) : 组合,将几个项组合为一个单元,这个单元可通过'*','+'等符号加以修饰,而且可以记住和这个组合相匹配的字符串以供此后的引用使用
(?:...) : 和上面的组合作用一样,但不记忆与改组相匹配的字符串
\n : 和第n个分组第一次匹配的字符相匹配
5.指定匹配位置
先来几个最简单的,匹配开头,结束和边界,什么意思呢?我们先列出来:
^ : 匹配字符串的开始,同样相当于匹配的是个位置
$ : 匹配字符串的结束
\b : 匹配单词的开始或结束,它相当于匹配的是个位置
\B : 反义,匹配非单词边界的位置
相当于匹配的是个位置?什么破描述,意思就是^是字符串的开头位置,$是字符串的末尾位置
/^JavaScript/ 明确要求字符串的开头必须是JavaScript 匹配字符串"JavaScript lalalallal"
/JavaScript$/ 明确要求字符串的结尾必须是JavaScript 匹配字符串"lalalallal JavaScript"
/\bJavaScript\b/ 明确要求字符串必须是JavaScript 匹配字符串"JavaScript"
还有最后一个概念就是零宽断言,这个名字让我一头雾水……
所谓零宽断言即进行位置匹配,以找到该位置前面或者后面所需要的匹配内容(下面这段直接搬运的,说的挺详细了)。
(?=exp) : 也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。 eg:
\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分,不包含ing,如singing and dancing只会匹配出来sing和danc。
(?<=exp): 即零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。eg:
(?<=\bre\w+\b)会匹配以re开头的单词的后半部分,如reading a book,只会匹配到ading。
举例:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)。
(?!exp) : 即零宽度负预测先行断言,断言此位置的后面不能匹配表达式exp。例如:如果想查找某个单词,它里面出现了字母q,
但q后面跟的不是字母u,通常可能会这样写\b\w*q[^u]\w*\b,多数时间好用,但它会匹配qq.com,faq ask这样的字串,
因为匹配时[^u]也会消耗掉一个字符。如果使用位置来匹配,如\b\w*q(?!u)\w*\b便可以完美解决。eg: \d{3}(?!\d)匹配三
位数字,而这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
(?<!exp): 即零宽度负回顾后发断言,断言此位置的前面不能匹配表达式exp。如(?<![a-z]>)\d{7}匹配前面不是小写字母的七位数字。
RegExp对象
1.RegExp实例属性
RegExp 的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息。
global :布尔值,表示是否设置了 g 标志。
ignoreCase :布尔值,表示是否设置了 i 标志。
lastIndex :整数,表示开始搜索下一个匹配项的字符位置,从0算起。
multiline :布尔值,表示是否设置了 m 标志。
source :正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。
var pattern1 = /\[bc\]at/i;
console.log(pattern1.global); // false
console.log(pattern1.ignoreCase); // true
console.log(pattern1.multiline); // false
console.log(pattern1.lastIndex); // 0
console.log(pattern1.source); // "\[bc\]at"
var pattern2 = new RegExp("\\[bc\\]at", "i");
console.log(pattern2.global); // false
console.log(pattern2.ignoreCase); // true
console.log(pattern2.multiline); // false
console.log(pattern2.lastIndex); // 0
console.log(pattern2.source); // "\[bc\]at"
2.RegExp实例方法
exec()
RegExp 对象的主要方法是 exec(),该方法是专门为捕获组而设计的。exec() 接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回 null。返回的数组虽然是 Array 的实例,但包含两个额外的属性:index 和 input。其中,index 表示匹配项在字符串中的位置,而 input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。请看下面的例子。
var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;
var matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches.input); // "mom and dad and baby"
console.log(matches[0]); // "mom and dad and baby"
console.log(matches[1]); // " and dad and baby"
console.log(matches[2]); // " and baby"
这里要注意,该数组第一个元素与正则表达式相匹配的字符串,余下的元素是与圆括号内的子表达式相匹配的字串。
对于 exec() 方法而言,即使在模式中设置了全局标志 g,它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用 exec() 将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用 exec() 则都会在字符串中继续查找新匹配项,如下面的例子所示。
var text = "cat, bat, sat, fat";
var pattern1 = /.at/;
// 非全局模式,第一次匹配
var matches = pattern1.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern1.lastIndex); // 0
// 非全局模式,第二次匹配
matches = pattern1.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern1.lastIndex); // 0
var pattern2 = /.at/g;
// 全局模式,第一次匹配
var matches = pattern2.exec(text);
console.log(matches.index); // 0
console.log(matches[0]); // cat
console.log(pattern2.lastIndex); // 0
// 全局模式,第二次匹配
matches = pattern2.exec(text);
console.log(matches.index); // 5
console.log(matches[0]); // bat
console.log(pattern2.lastIndex); // 8
test()
正则表达式的第二个方法是 test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回 true;否则,返回 false。在只想知道目标字符串与某个模式是否匹配,但不需要知道其文本内容的情况下,使用这个方法非常方便。因此,test() 方法经常被用在 if 语句中,如下面的例子所示。
var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)){
console.log("The pattern was matched.");
}
应用一
好,我们的第一个任务:如何检测用户输入文本的规范性:
1.手机号码
/^(13[0-9]|15[0|1|3|6|7|8|9]|18[8|9])\d{8}$/
观察上面的正则表达式,我们要求手机号必须是13位数字,其中有使用一个分组,分组内有选择和任意,表示手机号的前三位的逻辑
,后8位我们有用到重复,表示任意8位的数字。
2.身份证号
\d{17}[\dX]|\d{15}
身份证的要求是18位或者是15位,其中18位的身份证号最后一位是数字或者是X。这里用到了重复,任意等。
3.密码
我们通常要求用户密码大于等于6位,小于20位,包含字母、数字、下划线的组合。当然包含任意ASCII码字符也不是不可以。
[a-zA-Z0-9]([a-zA-Z]|\d|_){5,19}
第一个字符不希望是下划线。这里有用到任意,选择,分组,重复等。
应用二
第二个任务,提取url地址中的参数部分
document.location.href 返回完整的 URL.如:http://www.cftea.com/foo.asp?p=1
location.search 是从当前URL的?号开始的字符串
location.hash 是从当前URL的#号开始的字符串
getViewPageParam() {
var parameters = {};
[location.hash,location.search].map(function(c){
var str = c.replace(/^[\#|\?]/i,"");
str.split("&").map(function(c){
var unit = c.split("=");
if(unit[0]&&unit[1]){
parameters[unit[0]] = unit[1];
}
});
});
return parameters;
}
所以,获取到url后面的部分,我们先用repalce去掉#和?字符,然后利用split,以&为分割符将每个参数分割开来,之后又以=为分隔符将key与value分别作为parameters对象的key与value进行储存,这样方便存取。上面的函数就是获取url参数的完整方法。
总结
正则表达式是一门很深的知识,本篇仅仅是结合书本与一些博客自己做了一个入门与理解,虽然花了很多时间,但终究还是有不少东西没有说透彻,正则表达式的知识等本菜鸟有了一定的成长后会再来做一个更深层次地回顾与练习。
后面有内容摘自正则表达式学习笔记。