js之学习正则表达式
看了掘金的一个作者写的JS正则表达式完整教程 受益匪浅,感谢作者的无私奉献。在此,做下笔记。
目录
-
0. 目录
-
1. 正则表达式字符匹配
-
2. 正则表达式位置匹配
-
3. 正则表达式括号的作用
-
4. 操作符优先级
-
5. 元字符转义
-
6. search,split,match,replace,test,exec
1. 正则表达式字符匹配
1.1 字符组
[ab]匹配a或者b其中一个字符
1.1.1 常见的简写
\d: [0-9]。表示是一位数字。记忆方式:其英文是digit(数字)。
\D: [^0-9]。表示除数字外的任意字符。
\w: [0-9a-zA-Z_]。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。
\W: [^0-9a-zA-Z_]。非单词字符。
\s: [ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:s是space character的首字母。
\S: [^ \t\v\n\r\f]。 非空白符。
.: [^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。记忆方式:想想省略号...中的每个点,都可以理解成占位符,表示任何类似的东西。
[\d\D]、[\w\W]、[\s\S]和[^] : 任意字符
1.1.2 排除字符组
[^abc] 一个除"a"、"b"、"c"之外的任意一个字符,^(脱字符),表示求反
1.2 量词
1.2.1 简写
{m,n} : 表示至少出现m次,至多出现n次。
{m,} : 表示至少出现m次。
{m} : 等价于{m,m},表示出现m次。
? : 等价于{0,1},表示出现或者不出现。记忆方式:问号的意思表示,有吗?
+: 等价于{1,},表示出现至少一次。记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。
*: 等价于{0,},表示出现任意次,有可能不出现。记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。
1.2.2 贪婪匹配
var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", "1234", "12345", "12345"]
/\d{2,5}/,表示数字连续出现2到5次。会匹配2位、3位、4位、5位连续数字,你有几位我就匹配几位,能匹配多的绝对不给你匹配少的
有时贪婪不是一件好事,而惰性匹配,就是尽可能少的匹配:
1.2.3 惰性匹配
var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["12", "12", "34", "12", "34", "12", "34", "56"]
通过在量词后面加个问号就能实现惰性匹配,因此所有惰性匹配情形如下:
{m,n}?
{m,}?
??
+?
*?
1.3 多选分支 |
(p1|p2|p3),其中p1、p2和p3是子模式,用|(管道符)分隔,表示其中任何之一。
var regex = /good|goodbye/g;
var string = "goodbye";
console.log( string.match(regex) );
// => ["good"]
var regex = /goodbye|good/g;
var string = "goodbye";
console.log( string.match(regex) );
// => ["goodbye"]
也就是说,分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。
1.4 案例分析
1.4.1匹配时间 23:59 02:07
分析:
共4位数字,第一位数字可以为[0-2]。
当第1位为2时,第2位可以为[0-3],其他情况时,第2位为[0-9]。
第3位数字为[0-5],第4位为[0-9]
var regex = /^([01][0-9]|[2][0-3]):[0-5][0-9]$/;
1.4.2匹配日期 yyyy-mm-dd
分析:
年,四位数字即可,可用[0-9]{4}。
月,共12个月,分两种情况01、02、……、09和10、11、12,可用(0[1-9]|1[0-2])。
日,最大31天,可用(0[1-9]|[12][0-9]|3[01])。
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
console.log( regex.test("2017-06-10") );
// => true
1.4.3 window操作系统文件路径
案例 F:\study\javascript\regex\regular expression.pdf
分析:
整体模式是: 盘符:\文件夹\文件夹\文件夹<br/>
盘符:\:[a-zA-Z]+\ 盘符不能为空,\ 要转义
文件夹\:中间含有0或者多个文件夹的层级 文件名或者文件夹名,不能包含一些特殊字符,此时我们需要排除字符组[^\:<>|"?\r\n/]来表示合法字符,另外不能为空名,至少有一个字符,也就是要使用量词+ : [^\:<>|"?\r\n/+]*
最后一部分文件夹没有 \:[^\:*<>|"?\r\n/+]?
var regex = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/;
console.log( regex.test("F:\\study\\javascript\\regex\\regular expression.pdf") );
1.4.4 匹配id
提取出id="container"。 ``` var regex = /id=".*"/ var string = ''; console.log(string.match(regex)[0]); // => id="container" class="main" ``` .是通配符,本身就匹配双引号的,而量词*又是贪婪的,当遇到container后面双引号时,不会停下来,会继续匹配,直到遇到最后一个双引号为止。 解决之道,可以使用惰性匹配: ``` var regex = /id=".*?"/ var string = ''; console.log(string.match(regex)[0]); // => id="container"//优化
var regex = /id="[^"]*"/
var string = '
console.log(string.match(regex)[0]);
// => id="container"
##### 1.4.4 匹配带86的手机号 <br/>
13885821234 +8613885821234 08613885821234 (+86)13885821234 (086)13885821234
var string="13885821234 +8613885821234 08613885821234 (+86)13885821234 (086)13885821234"
var reg=/(+86|086|(+86)|(\086))?(1[34578][0-9]{9})/g
string.match(reg)
//["13885821234", "+8613885821234", "08613885821234", "(+86)13885821234", "13885821234"]
#### 2. <span id = "title-2">正则表达式位置匹配</span><br/>
#### 2.1 <span id = "title-2-1">什么是位置呢?</span><br/>
![](http://images2017.cnblogs.com/blog/1181900/201711/1181900-20171103144747763-1652231640.png)
#### 2.2 <span id = "title-2-2">如何匹配位置呢?</span><br/>
在ES5中,共有6个锚字符:<br/>
^ $ \b \B (?=p) (?!p)
#####2.2.1 ^和$ <br/>
^(脱字符)匹配开头,在多行匹配中匹配行开头。<br/>
$(美元符号)匹配结尾,在多行匹配中匹配行结尾。<br/>
var result = "hello".replace(/^|$/g, '#');
console.log(result);
// => "#hello#"
多行匹配
var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
console.log(result);
#####2.2.2 \b和\B <br/>
\w是字母数字或者下划线的中任何一个字符<br/>
\b是单词边界,具体就是\w和\W之间的位置,也包括\w和^之间的位置,也包括\w和$之间的位置。 <br/>
var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result);
// => "[#JS#] #Lesson_01#.#mp4#"
\B就是\b的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉\b,剩下的都是\B的。 <br/>
var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#');
console.log(result);
// => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"
#####2.2.3 (?=p)和(?!p) <br/>
(?=p),其中p是一个子模式,即p前面的位置 <br/>
(?=p),一般都理解成:要求接下来的字符与p匹配,但不能包括p的那些字符。<br/>
而在本人看来(?=p)就与^一样好理解,就是p前面的那个位置<br/>
(?!p),除了p前面的所有位置 <br/>
var result = "hello".replace(/(?=l)/g, '#');
console.log(result);
// => "he#l#lo"
var result = "hello".replace(/(?!l)/g, '#');
console.log(result);
// => "#h#ell#o#"
#### 2.3 <span id = "title-2-3">位置的特性</span><br/>
字符之间的位置,可以写成多个。<br/>
把位置理解空字符,是对位置非常有效的理解方式。<br/>
"hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "o" + "";
//true
"hello" == "" + "" + "hello"
//true
var result = /^^hello$$$/.test("hello");
console.log(result);
// => true
var result = /(?=he)^^he(?=\w)llo$\b\b$/.test("hello");
console.log(result);
// => true
#### 2.4. <span id = "title-2-4">相关案例</span><br/>
##### 2.4.1 不匹配任何东西的正则<br/>
/.^/ 此正则要求只有一个字符,但该字符后面是开头 <br/>
##### 2.4.2 数字的千位分隔符表示法<br/>
把"12345678",变成"12,345,678"<br/>
弄出最后一个逗号
var result = "12345678".replace(/(?=\d{3}$)/g, ',')
console.log(result);
// => "12345,678"
弄出所有逗号
var result = "12345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => "12345,678"
验证发现有问题
var result = "123456789".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => ",123,456,789"
怎么解决呢?我们要求匹配的到这个位置不能是开头。<br/>
我们知道匹配开头可以使用^,但要求这个位置不是开头怎么办?<br/>
easy,(?!^),你想到了吗?测试如下:<br/>
var string1 = "12345678",
string2 = "123456789";
reg = /(?!^)(?=(\d{3})+$)/g;
var result = string1.replace(reg, ',')
console.log(result);
// => "12,345,678"
result = string2.replace(reg, ',');
console.log(result);
// => "123,456,789"
把"12345678 123456789"替换成"12,345,678 123,456,789"。 <br/>
此时我们需要修改正则,把里面的开头^和结尾$,替换成\b: <br/>
var string = "12345678 123456789",
reg = /(?!\b)(?=(\d{3})+\b)/g;
var result = string.replace(reg, ',')
console.log(result);
// => "12,345,678 123,456,789"
其中(?!\b)怎么理解呢? <br/>
要求当前是一个位置,但不是\b前面的位置,其实(?!\b)说的就是\B。 <br/>
因此最终正则变成了:/\B(?=(\d{3})+\b)/g。 <br/>
##### 2.4.3 验证密码的问题<br/>
密码长度6-12位,由数字、小写字符和大写字母组成,但必须至少包括2种字符<br/>
不考虑“但必须至少包括2种字符”这一条件<br/>
var reg = /[1]{6,12}$/;
必须包含数字(?=.*[0-9]) <br/>
/(?=.*[0-9])^[0-9A-Za-z]{6,12}$/<br/>
对于这个正则,我们只需要弄明白(?=.*[0-9])^即可。<br/>
分开来看就是(?=.*[0-9])和^。<br/>
表示开头前面还有个位置(当然也是开头,即同一个位置,想想之前的空字符类比)。<br/>
(?=.*[0-9])表示该位置后面的字符匹配.*[0-9],即,有任何多个任意字符,后面再跟个数字。<br/>
var reg = /(?=.*[0-9])[2]{6,12}$/;
同时包含具体两种字符(?=.*[0-9])(?=.*[a-z]) <br/>
var reg = /(?=.[0-9])(?=.[a-z])[3]{6,12}$/;
我们可以把原题变成下列几种情况之一:<br/>
1. 同时包含数字和小写字母
2. 同时包含数字和大写字母
3. 同时包含小写字母和大写字母
4. 同时包含数字、小写字母和大写字母</br/>
var reg = /((?=.[0-9])(?=.[a-z])|(?=.[0-9])(?=.[A-Z])|(?=.[a-z])(?=.[A-Z]))[4]{6,12}$/;
console.log( reg.test("1234567") ); // false 全是数字
console.log( reg.test("abcdef") ); // false 全是小写字母
console.log( reg.test("ABCDEFGH") ); // false 全是大写字母
console.log( reg.test("ab23C") ); // false 不足6位
console.log( reg.test("ABCDEF234") ); // true 大写字母和数字
console.log( reg.test("abcdEF234") ); // true 三者都有
解法二
“至少包含两种字符”的意思就是说,不能全部都是数字,也不能全部都是小写字母,也不能全部都是大写字母。</br/>
那么要求“不能全部都是数字”,怎么做呢?(?!p)出马! <br/>
var reg = /(?!^[0-9]{6,12}$)^[0-9A-Za-z]{6,12}$/;
var reg = /(?![0-9]{6,12}$)(?![a-z]{6,12}$)(?![A-Z]{6,12}$)[0-9A-Za-z]{6,12}$/;
console.log( reg.test("1234567") ); // false 全是数字
console.log( reg.test("abcdef") ); // false 全是小写字母
console.log( reg.test("ABCDEFGH") ); // false 全是大写字母
console.log( reg.test("ab23C") ); // false 不足6位
console.log( reg.test("ABCDEF234") ); // true 大写字母和数字
console.log( reg.test("abcdEF234") ); // true 三者都有
#### 3. <span id = "title-3">正则表达式括号的作用</span><br/>
#### 3.1. <span id = "title-3-1">提取数据</span><br/>
以日期为例。假设格式是yyyy-mm-dd的<br/>
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,如RegExp.$1匹配结果的第一个引用()内容<br/>
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"
#### 3.2 <span id = "title-3-2">替换</span><br/>
把yyyy-mm-dd格式,替换成mm/dd/yyyy怎么做?<br/>
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$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() {
return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
console.log(result);
// => "06/12/2017"
#### 3.3 <span id = "title-3-3">反向引用</span><br/>
>
2016-06-12
2016/06/12
2016.06.12
>
//缺点:但也匹配"2016-06/12"这样的数据。
var regex = /\d{4}(-|/|.)\d{2}(-|/|.)\d{2}/;
//使用反向引用
var regex = /\d{4}(-|/|.)\d{2}\1\d{2}/;
\1,表示的引用之前的那个分组(-|\/|\.)。不管它匹配到什么(比如-),\1都匹配那个同样的具体某个字符。\2和\3同理<br/>
#### 3.4 <span id = "title-3-4">非捕获分组</span><br/>
只想要括号最原始的功能,但不会引用它,此时可以使用非捕获分组(?:p)<br/>
var regex = /(?:ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]
#### 3.5 <span id = "title-3-5">相关案例</span><br/>
##### 3.5.1 trim()方法
//法一
function tirm(str){
return str.replace(/^\s+|\s+$/g,"")
}
//法二 这里使用了惰性匹配?,不然也会匹配最后一个空格之前的所有空格的
function tirm(str){
return str.replace(/^\s(.?)\s$/g,"$1")
}
##### 3.5.2 将每个单词的首字母转换为大写
replace第一个参数是正则,第二个是字符串或者function,参数第一个是匹配正则的项,第二个是()引用返回的项,以此类推
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"
##### 3.5.3 驼峰化 <br/>
-moz-transform MozTransform <br/>
function camelize(str) {
return str.replace(/-_\s?/g,function(match,c){
return c?c.toUpperCase():''
})
}
console.log( camelize('-moz-transform') );
//中划线划
function dasherize(str) {
return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
}
console.log( dasherize('MozTransform') );
// => "-moz-transform"
单词的界定是,前面的字符可以是多个连字符、下划线以及空白符。正则后面的?的目的,是为了应对str尾部的字符可能不是单词字符,比如str是'-moz-transform '。<br/>
##### 3.5.4 html转义和反转义化 <br/>
// 将HTML特殊字符转换成等值的实体
function escapeHTML(str) {
var escapeChars = {
'¢' : 'cent',
'£' : 'pound',
'¥' : 'yen',
'€': 'euro',
'©' :'copy',
'®' : 'reg',
'<' : 'lt',
'>' : 'gt',
'"' : 'quot',
'&' : 'amp',
''' : '#39'
};
return str.replace(new RegExp('[' + Object.keys(escapeChars).join('') +']', 'g'), function(match) {
return '&' + escapeChars[match] + ';';
});
}
console.log( escapeHTML('
// => "<div>Blah blah blah</div>";
// 实体字符转换为等值的HTML。
function unescapeHTML(str) {
var htmlEntities = {
nbsp: ' ',
cent: '¢',
pound: '£',
yen: '¥',
euro: '€',
copy: '©',
reg: '®',
lt: '<',
gt: '>',
quot: '"',
amp: '&',
apos: '''
};
return str.replace(/&([^;]+);/g, function(match, key) {
if (key in htmlEntities) {
return htmlEntities[key];
}
return match;
});
}
console.log( unescapeHTML('<div>Blah blah blah</div>') );
// => "
##### 3.5.5 匹配成对html标签 <br/>
匹配一个开标签,可以使用正则<[^>]+>,<br/>
匹配一个闭标签,可以使用<\/[^>]+>,<br/>
要求匹配成对标签,那就需要使用反向引用
var regex = /<([^>]+)>[\d\D]*</\1>/;
#### 4. <span id = "title-4">操作符优先级</span><br/>
优先级从高到低<br/>
1. 转义符 \。
2. 括号和方括号 (...)、(?:...)、(?=...)、(?!...)、[...]。
3. 量词限定符 {m}、{m,n}、{m,}、?、*、+。
4. 位置和序列 ^ 、$、 \元字符、 一般字符。
5. 管道符 竖杠。
#### 5. <span id = "title-5">元字符转义</span><br/>
正则不会引发歧义,自然不需要转义。<br/>
需要视情况转义的字符 ^ $ . * + ? | \ / ( ) [ ] { } = ! : - ,
#### 6. <span id = "title-6">search,split,match,replace,test,exec </span><br/>
search和match,会把接受的字符串转换为正则<br/>
search有匹配结果返回第一个位置索引,无返回-1<br/>
match:没有g,返回的是标准匹配格式,即,数组的第一个元素是整体匹配的内容,接下来是分组捕获的内容,然后是整体匹配的第一个下标,最后是输入的目标字符串。<br/>
有g,返回的是所有匹配的内容。<br/>
当没有匹配时,不管有无g,都返回null。<br/>
exec:当正则没有g时,使用match返回的信息比较多。但是有g后,就没有关键的信息index了。
而exec方法就能解决这个问题,它能接着上一次匹配后继续匹配:<br/>
其中正则实例lastIndex属性,表示下一次匹配开始的位置。<br/>
var string = "2017.06.27";
var regex2 = /\b(\d+)\b/g;
console.log( regex2.exec(string) );
console.log( regex2.lastIndex);
console.log( regex2.exec(string) );
console.log( regex2.lastIndex);
console.log( regex2.exec(string) );
console.log( regex2.lastIndex);
console.log( regex2.exec(string) );
console.log( regex2.lastIndex);
// => ["2017", "2017", index: 0, input: "2017.06.27"]
// => 4
// => ["06", "06", index: 5, input: "2017.06.27"]
// => 7
// => ["27", "27", index: 8, input: "2017.06.27"]
// => 10
// => null
// => 0
split<br/>
第一,它可以有第二个参数,表示结果数组的最大长度:<br/>
var string = "html,css,javascript";
console.log( string.split(/,/, 2) );
// =>["html", "css"]
第二,正则使用分组时,结果数组中是包含分隔符的:<br/>
var string = "html,css,javascript";
console.log( string.split(/(,)/) );
// =>["html", ",", "css", ",", "javascript"]
replace <br/>
第一个参数是正则表达式<br/>
第二个参数是字符串时,如下的字符有特殊的含义:<br/>
>
$1,$2,...,$99 匹配第1~99个分组里捕获的文本
$& 匹配到的子串文本
$` 匹配到的子串的左边文本
$' 匹配到的子串的右边文本
$$ 美元符号
>
var text = "cat, bat, sat, fat";
var result = text.replace(/(.at)/g,"word ($1)"); //word (cat), word (bat), word (sat), word (fat)
function htmlEscape(text){
return text.replace(/[<>"&]/g, function(match, pos, originalText){
switch(match){
case "<": return "<";
case ">":return ">";
case "&":return "&";
case """:return """;
}
});
}
htmlEscape("<div class="gretting">")
// "<div class="gretting"></div>"