欢迎来到吴小小的专栏!

js之学习正则表达式

看了掘金的一个作者写的JS正则表达式完整教程 受益匪浅,感谢作者的无私奉献。在此,做下笔记。

目录

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('

Blah blah blah
') );
// => "<div>Blah blah blah</div&gt";


// 实体字符转换为等值的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>') );
// => "

Blah blah blah
"


##### 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>"


  1. 0-9A-Za-z ↩︎

  2. 0-9A-Za-z ↩︎

  3. 0-9A-Za-z ↩︎

  4. 0-9A-Za-z ↩︎

posted @ 2017-11-06 15:57  吴小小  Views(201)  Comments(0Edit  收藏  举报