JS模块知识----正则表达式
正则表达式(regular expression)是一个描述字符模式的对象,ECMAScript的RegExp 类表示正则表达式,而String和RegExp都定义了使用正则表达式进行强大的模式匹配和文本检索与替换的函数。
有2种创建对象的方式(推荐使用方式二)
方式1:使用关键字new创建
var patt = new RegExp(pattern,modifiers);
参数1:正则表达式的模式。字符串形式
参数2:模式修饰符。用于指定全局匹配、区分大小写的匹配和多行匹配
<script type="text/javascript"> /* 创建了一个正则表达式 参数1:模式是:girl,意思是说可以匹配 "girl"这样的字符串 参数2:模式修饰符:gi g代表全局匹配 i代表不区分大小写 */ var pa = new RegExp("girl", "gi"); //测试参数中的字符串"你好我的girl" 是否与匹配模式匹配。 var isExist = pa.test("你好我的girl"); // 在本例中,是匹配的,这个字符串包含girl,所以返回true alert(isExist); //true </script>
方式2:使用正则表达式直接量(写法简单,性能好,)
var pa = /pattern/modifiers;
两个/中间的表示正则表达式的模式,最后一个/的后面是模式修饰符
例如:上面的例子可以这样写 var pa = /girl/gi;
<script type="text/javascript"> var pa = /girl/gi; alert(pa.test("厉害了我的girl")); //true </script>
正则表达式基本知识
基本元字符
.
: 匹配除了换行符之外的任何单个字符\
: 在非特殊字符之前的反斜杠表示下一个字符是特殊的,不能从字面上解释。例如,没有前\的'b'通常匹配小写'b',无论它们出现在哪里。如果加了'\',这个字符变成了一个特殊意义的字符,反斜杠也可以将其后的特殊字符,转义为字面量。例如,模式/a*/
代表会匹配 0 个或者多个 a。相反,模式/a\*/
将 '*' 的特殊性移除,从而可以匹配像"a*"
这样的字符串。|
: 逻辑或操作符[]
:定义一个字符集合,匹配字符集合中的一个字符,在字符集合里面像.
,\
这些字符都表示其本身[^]
:对上面一个集合取非-
:定义一个区间,例如[A-Z]
,其首尾字符在 ASCII 字符集里面
数量元字符
{m,n}
:匹配前面一个字符至少 m 次至多 n 次重复,还有{m}
表示匹配 m 次,{m,}
表示至少 m 次+
: 匹配前面一个表达式一次或者多次,相当于{1,}
,记忆方式追加(+),起码得有一次*
: 匹配前面一个表达式零次或者多次,相当于{0,}
,记忆方式乘法(*),可以一次都没有?
: 单独使用匹配前面一个表达式零次或者一次,相当于{0,1}
,记忆方式,有吗?,有(1)或者没有(1),如果跟在任何量词*
,+
,?
,{}
后面的时候将会使量词变为非贪婪模式(尽量匹配少的字符),默认是使用贪婪模式。比如对 "123abc" 应用/\d+/
将会返回 "123",如果使用/\d+?/
,那么就只会匹配到 "1"。
位置元字符
^
: 单独使用匹配表达式的开始- $ : 匹配表达式的结束
\b
:匹配单词边界\B
:匹配非单词边界(?=p)
:匹配 p 前面的位置(?!p)
:匹配不是 p 前面的位置
特殊元字符
\d
:[0-9]
,表示一位数字,记忆方式 digit\D
:[^0-9]
,表示一位非数字\s
:[\t\v\n\r\f]
,表示空白符,包括空格,水平制表符(\t
),垂直制表符(\v
),换行符(\n
),回车符(\r
),换页符(\f
),记忆方式 space character\S
:[^\t\v\n\r\f]
,表示非空白符\w
:[0-9a-zA-Z]
,表示数字大小写字母和下划线,记忆方式 word\W
:[^0-9a-zA-Z]
,表示非单词字符
标志字符
g
: 全局搜索 记忆方式globali
:不区分大小写 记忆方式 ignorem
:多行搜索
正则表达式进阶知识
零:反义
有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义
\W匹配任意不是字母,数字,下划线,汉字的字符
\S匹配任意不是空白符的字符
\D匹配任意非数字的字符
\B匹配不是单词开头或结束的位置
[^x]匹配除了x以外的任意字符
[^aeiou]匹配除了aeiou这几个字母以外的任意字符
例如
<a[^>]+>匹配用尖括号括起来的以a开头的字符串。
一:分支
所谓分支,是指有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用 |
把不同的规则分隔开。例如:
0\d{2}-\d{8}|0\d{3}-\d{7}
匹配两种以 连字号 分隔的电话号码:
一种是3位区号,8位本地号(如010-12345678),
一种是4位区号,7位本地号(0376-2233445)。
使用分枝条件时,要注意各个条件的顺序:原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了
以美国的邮政编码规则为例
美国邮编的规则是5位数字,或者用 连字号 间隔的9位数!
\d{5}|\d{5}-\d{4}//错误 \d{5}-\d{4}|\d{5}//正确 //上面的两个表达式是一样的,只不过前后顺序不同 //第一个表达式 就只会匹配5位的邮编(以及9位邮编的前5位)。规则不全!!这就是需要注意的地方------
体会:当条件涉及到包含的时候(9位数的邮编包含5位数的),应该将大条件放在前面!!
二:分组
重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?
用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了
(\d{1,3}\.){3}\d{1,3}
一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字,(\d{1,3}\.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。
不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
常用分组语法
分类 | 代码/语法 | 说明 |
---|---|---|
捕获 | (exp) | 匹配exp,并捕获文本到自动命名的组里 |
(?<name>exp) | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) | |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
零宽断言 | (?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 | |
(?!exp) | 匹配后面跟的不是exp的位置 | |
(?<!exp) | 匹配前面不是exp的位置 | |
注释 | (?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
三:断言(理解为条件)
x(?=exp) |
零宽度正先行断言。exp紧跟x的情况下匹配x。例如,/w+(?=/d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。 |
x(?!exp) |
零宽度负先行断言。x后无exp紧随的情况下匹配x。例如,例如,/w+(?!/d) 与后不跟数字的单词匹配,而不与该数字匹配 。 |
(?<=exp)x |
零宽度正后发断言。x紧随exp的情况下匹配x。例如,(?<=19)99 与跟在 19 后面的 99 的实例匹配。此构造不会回溯。 |
(?<!exp)x |
零宽度负后发断言。x不紧随y的情况下匹配x。例如,(?<!19)99 与不跟在 19 后面的 99 的实例匹配 |
要理解上面的名词,先理解几个小的定义
断言:就是指明某个字符串前边或者后边,将会出现满足某种规律的字符串。这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言
假如目标字符串后边有条件,可以理解为目标字符串在前,就用先行断言,放在目标字符串之后。
假如目标字符串前边有条件,可以理解为目标字符串在后,就用后发断言,放在目标字符串之前。
假如指定满足某个条件,就是正。
假如指定不满足某个条件,就是负。
断言只是条件,帮你找到真正需要的字符串,本身并不会匹配!
其实掌握了规律,就很简单了,无论是先行还是后发,都是相对于目标字符串而言的。简单记忆:目标在前就先行,目标在后就断发!
结合实例理解
//要求: //查找 I'm singing while you're dancing.中以ing结尾的单词的前面部分(除了ing以外的部分) //思路:要查找的目标在前,就用先行。正则表达式的顺序就是 目标+先行断言! \b\w+(?=ing\b)
//匹配sing和danc
//要求:查找reading a book 中 以re开头的单词的后半部分(除了re以外的部分) //思路:查找的目标在后 就用后发断言 正则表达式的顺序就是 后发断言 + 目标 (?<=\bre)\w+\b //匹配 ading
例如,对于/\d+(?!\。)/,数字后没有跟随小数点的情况下才会得到匹配。对于/\d+(?!\.)/.exec(3.141),匹配‘141’而不是‘3’。
对于/(?<!-)\d+/,数字紧随-符号的情况下才会得到匹配。对于/(?<!-)\d+/.exec(3) ,“3”得到匹配。 而/(?<!-)\d+/.exec(-3)的结果无匹配,这是由于数字之前有-符号。
四:贪婪与懒惰
a.*b
,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。?
即可代码/语法 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
匹配中文字符的正则表达式: [\u4e00-\u9fa5]
正则表达式使用
test 和 exec 的正则对象的方法
test()方法
用法:正则.test(字符串)
参数:要匹配的字符串
返回值:匹配成功返回true,失败返回false
在只想知道目标字符串与某个模式是否匹配,但不需要知道其文本内容的情况下,使用这个方法非常方便。因此, test() 方法经常被用在 if 语句中。
<script type="text/javascript"> var pa = /girl/gi; if(pa.test("厉害了我的girl")){ alert("这个女孩和你很配"); }else { alert("你注定没有女孩去匹配"); } </script>
exec()方法
该方法专门为捕获组而设计的
- 参数:要匹配的字符串
-
返回值:返回的是一个数组。如果不匹配则返回null
关于返回值数组的说明:
-
它确实是Array的实例。
- 但是这个数组有两个额外的属性:index和input
- index:表示匹配的字符串在源字符串中的索引
- input:表示匹配的源字符串。
- 数组的第一项目是与整个模式匹配的字符串,其他项是与模式中捕获组匹配的字符串
- 如果没有捕获组,则数组中只有第一项。关于捕获组的概念以后再说
<script type="text/javascript"> var pa = /girl/gi; var testStr = "myGirl, yourgirl, hisgIrl"; var girls = pa.exec(testStr); //捕获 alert(girls.length + ":" + (girls instanceof Array)); //正则表达式没有捕获组,所以数组长度为1 alert(girls[0]); //第一次捕获的是 Girl //因为我们是用的全局匹配,所以此次匹配的时候从上次匹后的位置开始继续匹配 alert(pa.exec(testStr)[0]); // girl alert(pa.exec(testStr)); // gIrl alert(pa.exec(testStr)); //继续向后没有匹配的字符串,所以返回null // 返回null,如果继续再匹配,则会回到字符串的开始,重写开始匹配。 alert(pa.exec(testStr)); // Girl // ...开启新一轮匹配 </script>
search()
在字符串搜索符合正则的内容,搜索到就返回出现的位置(从0开始,如果匹配的不只是一个字母,那只会返回第一个字母的位置), 如果搜索失败就返回 -1
用法:字符串.search(正则)
在字符串中查找复合正则的内容。忽略大小写:i——ignore(正则中默认是区分大小写的 如果不区分大小写的话,在正则的最后加标识 i )
例子:在字符串中找字母b,且不区分大小写
var str = 'abcdef'; var re = /B/i; //var re = new RegExp('B','i'); 也可以这样写 alert( str.search(re) ); // 1
match()
用法: 字符串.match(正则)
量词:+ 至少出现一次 匹配不确定的次数(匹配就是搜索查找的意思)
全局匹配:g——global(正则中默认,只要搜索到复合规则的内容就会结束搜索 )
例子:找出指定格式的所有数字,如下找到 123,54,33,879
var str = 'haj123sdk54hask33dkhalsd879'; var re = /\d+/g; // 每次匹配至少一个数字 且全局匹配
如果不是全局匹配,当找到数字123,它就会停止了。就只会弹出123.加上全局匹配,就会从开始到结束一直去搜索符合规则的。
如果没有加号,匹配的结果就是1,2,3,5,4,3,3,8,7,9并不是我们想要的,有了加号,每次匹配的数字就是至少一个了。 alert( str.match(re) ); // [123,54,33,879]
replace()
:查找符合正则的字符串,就替换成对应的字符串。返回替换后的内容。
用法: 字符串.replace(正则,新的字符串/回调函数)(在回调函数中,第一个参数指的是每次匹配成功的字符)
| : 或的意思 。
//首先是简单的字符串替换 var str = "我爱北京天安门,天安门上太阳升。"; var re = /北京|天安门/g; // 找到北京 或者天安门 全局匹配 var str2 = str.replace(re,'*'); alert(str2) //我爱**,*上太阳升 //这种只是把找到的变成了一个*,并不能几个字就对应几个*。 //接着是 回调函数 var str = "我爱北京天安门,天安门上太阳升。"; var re = /北京|天安门/g; // 找到北京 或者天安门 全局匹配 var str2 = str.replace(re,function(str){ alert(str); //用来测试:函数的第一个参数代表每次搜索到的符合正则的字符,所以第一次str指的是北京 第二次str是天安门 第三次str是天安门 var result = ''; for(var i=0;i<str.length;i++){ result += '*'; } return result; //所以搜索到了几个字就返回几个* }); alert(str2) //我爱*****,***上太阳升 //整个过程就是,找到北京,替换成了两个*,找到天安门替换成了3个*,找到天安门替换成3个*。
容易有误解的点
1.()、[]与|
[]:集合操作符,表示一系列字符的任意一个
例如:/[abc]/ 表示a、b、c中的任意一个能匹配就可以了
但是:对于/[a|b]/呢?
一个常见的误区是感觉/[a|b]/表示要匹配a或者b,其实是a、b或者|中的任意一个
/[a|b]/.test('|') === true /(a|b)/.test('|') ===false
圆括号中的|是或的意思,表示要匹配()以|分割的两边的整体,注意是整体
/(abc|abd)/.test('ab') ===false /(abc|abd)/.test('abc') ===true /(abc|abd)/.test('abd') ===true
常用正则表示
1、检查邮政编码
var pattern = /[1-9][0-9]{5}/; //共6位数字,第一位不能为0
var str = '224000';
alert(pattern.test(str));
2、检查文件压缩包
var pattern = /[\w]+\.zip|rar|gz/; //\w 表示所有数字和字母加下划线
var str = '123.zip'; //\.表示匹配.,后面是一个选择
alert(pattern.test(str));
3、删除多余空格
var pattern = /\s/g; //g 必须全局,才能全部匹配
var reg=new RegExp('\\s+','g');
var str = '111 222 333';
var result = str.replace(pattern,''); //把空格匹配成无空格
alert(result);
4、删除空格
var pattern = /^\s+/;
var str = ' goo gle ';
alert(str+" "+str.length);
var result = str.replace(pattern, '');
alert(result+" "+result.length);
pattern = /\s+$/;
result = result.replace(pattern, '');
alert(result+" "+result.length);
pattern = /\s+/g;
result = result.replace(pattern, '');
alert(result+" "+result.length);
5、简单的电子邮件验证
var pattern = /^([a-zA-Z0-9_\.\-]+)@([a-zA-Z0-9_\.\-]+)\.([a-zA-Z]{2,4})$/;
var str = 'yc60.com@gmail.com';
alert(pattern.test(str));
var pattern = /^([\w\.\-]+)@([\w\.\-]+)\.([\w]{2,4})$/;
var str = 'yc60.com@gmail.com';
alert(pattern.test(str));
6、校验中文
^[\\u4e00-\\u9fa5]{0,}$ //字符串仅能是中文。
7、由数字、26个英文字母或下划线组成的字符串
^\\w+$
8、校验身份证号码
15位:^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$
18位:^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$
9、金额校验,精确到2位小数。
^[0-9]+(.[0-9]{2})?$
2018-2-20 补充:总结一下括号的作用
1.分组和分支结构
1.1分组
例如:/a+/匹配连续出现的“a”,而要匹配连续出现的“ab”时,需要使用/(ab)+/。此处括号是提供分组功能,使量词“+”作用于“ab”这个整体
1.2分支(也就是多选)
多选分支结构(p1|p2)中,此处括号的作用也是不言而喻的,提供了子表达式的所有可能
var regex = /^I love (JavaScript|Regular Expression)$/; console.log( regex.test("I love JavaScript") ); // true console.log( regex.test("I love Regular Expression") ); // true
2 分组引用
括号一个重要的作用,我们就可以进行数据提取,以及更强大的替换操作。
而要使用它带来的好处,必须配合使用实现环境的API。
//以日期为例。假设格式是yyyy-mm-dd的,我们可以先写一个简单的正则: var regex = /\d{4}-\d{2}-\d{2}/; //再修改成括号版的 var regex = /(\d{4})-(\d{2})-(\d{2})/; //以下就是 这样写的好处(实例) //eg1 提取出年、月、日 var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; console.log( string.match(regex) ); // => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"] //同时,也可以使用构造函数的全局属性$1至$9来获取: var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; regex.test(string); // 正则操作即可,例如 //regex.exec(string); //string.match(regex); console.log(RegExp.$1); // "2017" console.log(RegExp.$2); // "06" console.log(RegExp.$3); // "12" //eg2 替换 //把yyyy-mm-dd格式,替换成mm/dd/yyyy var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; var result = string.replace(regex, "$2/$3/$1");//replace中的,第二个参数里用$1、$2、$3指代相应的分组 console.log(result); // "06/12/2017" 上述的写法等价于 var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; var result = string.replace(regex, function() { return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1; }); console.log(result); // "06/12/2017" 也等价于 var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; var result = string.replace(regex, function(match, year, month, day) { return month + "/" + day + "/" + year; }); console.log(result); // "06/12/2017"
3.反向引用
比如要写一个正则支持匹配如下三种格式:
2016-06-12
2016/06/12
2016.06.12
最先可能想到的正则是:
var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // true
其中/和.需要转义。虽然匹配了要求的情况,但也匹配"2016-06/12"这样的数据。
假设我们想要求分割符前后一致怎么办?此时需要使用反向引用:
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
注意里面的\1,表示的引用之前的那个分组(-|\/|\.)。不管它匹配到什么(比如-),\1都匹配那个同样的具体某个字符。
我们知道了\1的含义后,那么\2和\3的概念也就理解了,即分别指代第二个和第三个分组。
4. 非捕获分组
如果只想要分组的功能,但不会引用它,即,既不在API里引用分组,也不在正则里反向引用。此时可以使用非捕获分组(?:p)
匹配一个开标签,可以使用正则<[^>]+>,
匹配一个闭标签,可以使用<\/[^>]+>,
var regex = /<([^>]+)>[\d\D]*<\/\1>/;//加入反向引用 匹配成对的标签 var string1 = "<title>regular expression</title>"; var string2 = "<p>laoyao bye bye</p>"; var string3 = "<title>wrong!</p>"; console.log( regex.test(string1) ); // true console.log( regex.test(string2) ); // true console.log( regex.test(string3) ); // false
5.2将每个单词的首字母转换为大写
function titleize(str) { return str.toLowerCase().replace(/(?:^|\s)\w/g, function(c) { return c.toUpperCase(); }); } console.log( titleize('my name is epeli') ); // "My Name Is Epeli"
5.3字符串trim方法模拟
//方法一:匹配到开头和结尾的空白符,然后替换成空字符 function trim(str) { return str.replace(/^\s+|\s+$/g, ''); } console.log( trim(" foobar ") ); // "foobar" //方法二:匹配整个字符串,然后用引用来提取出相应的数据 使用了惰性匹配*?,不然也会匹配最后一个空格之前的所有空格的 function trim(str) { return str.replace(/^\s+(.*?)\s+$/g, "$1"); } console.log( trim(" foobar ") ); // "foobar"
5.4获取链接 https://www.baidu.com?name=jawil&age=23 name的value值
function getParamName(attr) { let match = RegExp(`[?&]${attr}=([^&]*)`) //分组运算符是为了把结果存到exec函数返回的结果里 .exec(window.location.search) //["?name=jawil", "jawil", index: 0, input: "?name=jawil&age=23"] return match && decodeURIComponent(match[1].replace(/\+/g, ' ')) // url中+号表示空格,要替换掉 } console.log(getParamName('name')) // "jawil"
6666