正则表达式剖析-案例

一直都是在用正则表达式,没有好好的汇总过。正好,遇到很多小算法,可以作为案例补充。

正则表达式的定义:

正则表达式是由普通字符和特殊字符(也叫元字符或限定符)组成的文字模板。

元字符:

.   匹配除换行符(\n\r)以外的任意字符
\d  匹配数字,等价于字符组[0-9]
\w  匹配字母,数字,下划线或汉字
\s  匹配任意的空白符(包括制表符,空格,换行等)
\b  匹配单词开始或结束的位置
^   匹配行首
$ 匹配行尾

反义元字符

\D  匹配非数字的任意字符,等价于[^0-9]
\W  匹配除字母,下划线或汉字之外的任意字符
\S  匹配非空白的任意字符
\B  匹配非单词开始或结束的位置
[^x]匹配除x以外的任意字符

重复限定符:

限定符共有6个,假设重复次数为x次,那么将有如下规则:

*   x>=0
+   x>=1
?   x=0 or x=1
{n} x=n
{n,}    x>=n
{n,m}   n<=x<=m

字符组

[…] 匹配中括号内字符之一。 如: [xyz] 匹配字符 x,y 或 z. 如果中括号中包含元字符,则元字符降级为普通字符,不再具有元字符的功能,如 [+.?] 匹配 加号,点号或问号.

排除性字符组

[^…] 匹配任何未列出的字符,. 如: [^x] 匹配除x以外的任意字符.

多选结构

| 就是或的意思,表示两者中的一个. 如: a|b 匹配a或者b字符.

括号

括号 常用来界定重复限定符的范围,以及将字符分组. 如: (ab)+ 可以匹配abab..等,其中 ab 便是一个分组.括号起到字符分组或者捕获作用。

转义字符

通常 * + ? | { [ ( ) ] }^ $ . # 和 空白 这些字符都需要转义.

操作符的运算优先级

1、转义符
2、(),(?:),(?=),[] 圆括号或方括号
3、*,+,?,{n},{n,},m} 限定符
4、^,$ 位置
5、| "或" 操作

修饰符

javaScript中正则表达式默认有如下五种修饰符:

g (全文查找) i (忽略大小写查找) m (多行查找) y (ES6新增的粘连修饰符) u (ES6新增) 

常用的正则表达式

汉字: ^[u4e00-u9fa5]{0,}$ Email: ^w+([-+.]w+)*@w+([-.]w+)*\.w+([-.]w+)*$ URL: ^https?://([w-]+.)+[w-]+(/[w-./?%&=]*)?$ 手机号码: ^1d{10}$ 身份证号: ^(d{15}|d{17}(d|X))$ 中国邮政编码: [1-9]d{5}(?!d) (邮政编码为6位数字)

密码验证

密码验证是常见的需求,一般来说,常规密码大致会满足规律: 6-16位,字母,字符至少包含两种,同时不能包含中文和空格.
如下便是常规密码验证的正则描述:

var reg = /(?!^[0-9]+$)(?!^[A-z]+$)(?!^[^A-z0-9]+$)^[^\s\u4e00-\u9fa5]{6,16}$/;

贪婪模式与非贪婪模式

默认情况下,所有的限定词都是贪婪模式,表示尽可能多的去捕获字符; 而在限定词后增加?,则是非贪婪模式,表示尽可能少的去捕获字符. 如下:

var str = "aaab",reg1 = /a+/,//贪婪模式
    reg2 = /a+?/;//非贪婪模式
console.log(str.match(reg1)); //["aaa"],由于是贪婪模式,捕获了所有的a
console.log(str.match(reg2)); //["a"],由于是非贪婪模式,只捕获到第一个a

实际上,非贪婪模式非常有效,特别是当匹配html标签时. 比如匹配一个配对出现的div,方案一可能会匹配到很多的div标签对,而方案二则只会匹配一个div标签对.

var str = "<div class='v1'><div class='v2'>test</div><input type='text'/></div>"; var reg1 = /<div.*<\/div>/; //方案一,贪婪匹配 var reg2 = /<div.*?<\/div>/;//方案二,非贪婪匹配 console.log(str.match(reg1));//"<div class='v1'><div class='v2'>test</div><input type='text'/></div>" console.log(str.match(reg2));//"<div class='v1'><div class='v2'>test</div>"

区间量词的非贪婪模式

一般情况下,非贪婪模式,我们使用的是 “*?”,或 “+?” 这种形式,还有一种是 “{n,m}?”.

区间量词”{n,m}” 也是匹配优先,虽有匹配次数上限,但是在到达上限之前,它依然是尽可能多的匹配,而 “{n,m}?” 则表示在区间范围内,尽可能少的匹配.

需要注意的是:

能达到同样匹配结果的贪婪与非贪婪模式,通常是贪婪模式的匹配效率较高.

分组

正则的分组主要通过小括号来实现,括号包裹的子表达式作为一个分组,括号后可以紧跟限定词表示重复次数.

如下,小括号内包裹的abc便是一个分组:

/(abc)+/.test("abc123") == true

那么分组有什么用呢? 一般来说,分组是为了方便的表示重复次数,除此之外,还有一个作用就是用于捕获,.

捕获性分组

捕获性分组,通常由一对小括号加上子表达式组成. 捕获性分组会创建反向引用,每个反向引用都由一个编号或名称来标识,js中主要是通过
$+编号 或者 \+编号 表示法进行引用.

var color = "#808080";
var output = color.replace(/#(\d+)/,"$1"+"~~");//自然也可以写成 "$1~~"
console.log(RegExp.$1);//808080
console.log(output);//808080~~

以上,(\d+) 表示一个捕获性分组,"RegExp.$1" 指向该分组捕获的内容. $编号 这种引用通常在正则表达式之外使用. \编号 这种引用却可以在正则表达式中使用,可用于匹配不同位置相同部分的子串.

var url = "www.google.google.com";
var re = /([a-z]+)\.\1/;
console.log(url.replace(re,"$1"));//"www.google.com"

以上,相同部分的”google”字符串只被替换一次.

非捕获性分组

非捕获性分组,通常由一对括号加上"?:"加上子表达式组成,非捕获性分组不会创建反向引用,就好像没有括号一样.

var color = "#808080";
var output = color.replace(/#(?:\d+)/,"$1"+"~~");
console.log(RegExp.$1);//""
console.log(output);//$1~~
"#808080".match(/#(?:\d+)/); //["#808080"]

以上,(?:d+) 表示一个非捕获性分组,由于分组不捕获任何内容,所以,RegExp.$1 就指向了空字符串. 同时,由于$1的反向引用不存在,因此最终它被当成了普通字符串进行替换.

正则表达式高阶技能-零宽断言(环视)

零宽断言,又叫环视. 环视只进行子表达式的匹配,匹配到的内容不保存到最终的匹配结果,由于匹配是零宽度的,故最终匹配到的只是一个位置.

环视按照方向划分,有顺序和逆序两种(也叫前瞻和后瞻),按照是否匹配有肯定和否定两种,组合之,便有4种环视. 4种环视并不复杂,如下便是它们的描述.

(?:pattern) 非捕获性分组,匹配pattern的位置,但不捕获匹配结果.也就是说不创建反向引用,就好像没有括号一样.

(?=pattern) 顺序肯定环视,匹配后面是pattern 的位置,不捕获匹配结果.
(?!pattern) 顺序否定环视,匹配后面不是 pattern 的位置,不捕获匹配结果.
(?<=pattern)    逆序肯定环视,匹配前面是 pattern 的位置,不捕获匹配结果.
(?<!pattern)    逆序否定环视,匹配前面不是 pattern 的位置,不捕获匹配结果.

非捕获性分组由于结构与环视相似,故列在表中,以做对比. 以上4种环视中,目前 javaScript 中只支持前两种,也就是只支持 顺序肯定环视 和 顺序否定环视.

顺序肯定环视:
'Windows(?=2000)'
匹配 "Windows2000" 中的 "Windows"; 不匹配 "Windows3.1" 中的 "Windows"

顺序否定环视:
'Windows(?!2000)'
匹配 "Windows3.1" 中的 "Windows"; 不匹配 "Windows2000" 中的 "Windows"

实例:

var str = "123abc789",s;
//没有使用环视,abc直接被替换
s = str.replace(/abc/,456);
console.log(s); //123456789

//使用了顺序肯定环视,捕获到了a前面的位置,所以abc没有被替换,只是将3替换成了3456
s = str.replace(/3(?=abc)/,3456);
console.log(s); //123456abc789

//使用了顺序否定环视,由于3后面跟着abc,不满意条件,故捕获失败,所以原字符串没有被替换
s = str.replace(/3(?!abc)/,3456);
console.log(s); //123abc789

正则表达式之一 :exec 方法

用正则表达式模式在字符串中查找,并返回该查找结果的第一个值(数组),如果匹配失败,返回null。

rgExp.exec(str)
参数

  • rgExp
    必选项。包含正则表达式模式和可用标志的正则表达式对象。
  • str
    必选项。要在其中执行查找的 String 对象或字符串文字。

返回数组

  • input属性:整个被查找的字符串的值;

  • index属性:匹配结果所在的位置(位);

  • arr:结果值,arr[0]全匹配结果,arr[1,2…]为表达式内()的子匹配,由左至右为1,2…。

注意:在匹配后,rgExp 的 lastIndex 属性被设置为匹配文本的最后一个字符的下一个位置。lastIndex并不在返回对象的属性中,而是正则表达式对象的属性。

例子1:不含子表达式的正则表达式exec方法循环应用

!function RegExpTest(){ 
    var src="http://sumsung753.blog.163.com/blog/I love you!"; 
    // 注意g将全文匹配,不加将永远只返回第一个匹配。 
    var re = /\w+/g; 
    var arr; 
    // exec使arr返回匹配的第一个,while循环一次将使re在g作用寻找下一个匹配。 
    while((arr = re.exec(src)) !=null){ 
        document.write(arr.index + "-" + re.lastIndex + ":" + arr + "<br/>"); 
        for(key in arr){ 
            document.write(key + "=>" + arr[key] + "<br/>"); 
        } 
        document.write("<br/>"); 
    } 
}()

结果:

0-4:http
0=>http
index=>0
input=>http://sumsung753.blog.163.com/blog/I love you!

7-17:sumsung753
0=>sumsung753
index=>7
input=>http://sumsung753.blog.163.com/blog/I love you!

18-22:blog
0=>blog
index=>18
input=>http://sumsung753.blog.163.com/blog/I love you!

23-26:163
0=>163
index=>23
input=>http://sumsung753.blog.163.com/blog/I love you!

27-30:com
0=>com
index=>27
input=>http://sumsung753.blog.163.com/blog/I love you!

31-35:blog
0=>blog
index=>31
input=>http://sumsung753.blog.163.com/blog/I love you!

36-37:I
0=>I
index=>36
input=>http://sumsung753.blog.163.com/blog/I love you!

38-42:love
0=>love
index=>38
input=>http://sumsung753.blog.163.com/blog/I love you!

43-46:you
0=>you
index=>43
input=>http://sumsung753.blog.163.com/blog/I love you!

exec默认只返回匹配结果的第一个值,比如上例如果不用while循环,将只返回 'http'(尽管后面的sumsung753等都符合表达式),无论re表达式用不用全局标记g

但是如果为正则表达式设置了全局标记g,exec从上次匹配结束的位置开始查找。如果没有设置全局标志,exec依然从字符串的起始位置开始搜索

利用这个特点可以反复调用exec遍历所有匹配,等价于match具有g标志。当然,如果正则表达式忘记用g,而又用循环(比如:while、for等),exec将每次都循环第一个,造成死循环。

如果正则表达式中包含子表达式,那么输出结果将包含子匹配项

例子2:包含子表达式的正则表达式exec方法应用

!function execDemo(){ 
    var r,re; // 声明变量。 
    var s = "The rain in Spain falls mainly in the plain"; 
    re = /[\w]*(ai)n/ig; 
    r = re.exec(s); 
    document.write(r + "<br/>");
    for(key in r){ 
        document.write(key + "-" + r[key] + "<br/>"); 
    } 
}()
//输出结果如下:
rain,ai
0-rain
1-ai
index-4
input-The rain in Spain falls mainly in the plain

例子3:包含子表达式的正则表达式exec方法循环应用

场景回顾

获取html片段

假如现在,js 通过 ajax 获取到一段 html 代码如下:

var responseText = "<div data='dev.xxx.txt'></div><img src='dev.xxx.png' />";

现我们需要替换img标签的src 属性中的 “dev”字符串 为 “test” 字符串.

① 由于上述 responseText 字符串中包含至少两个子字符串 “dev”,显然不能直接 replace 字符串 “dev”为 “test”.

② 同时由于 js 中不支持逆序环视,我们也不能在正则中判断前缀为 “src=’”,然后再替换”dev”.

③ 我们注意到 img 标签的 src 属性以 “.png” 结尾,基于此,就可以使用顺序肯定环视.

如下:

var reg = /dev(?=[^']*png)/; //为了防止匹配到第一个dev,通配符前面需要排除单引号或者是尖括号
var str = responseText.replace(reg,"test");
console.log(str);//<div data='dev.xxx'></div><img src='test.xxx.png' />

当然,以上不止顺序肯定环视一种解法,捕获性分组同样可以做到. 那么环视高级在哪里呢? 环视高级的地方就在于它通过一次捕获就可以定位到一个位置,对于复杂的文本替换场景,常有奇效,而分组则需要更多的操作.

千位分割符

千位分隔符,顾名思义,就是数字中的逗号. 参考西方的习惯,数字之中加入一个符号,避免因数字太长难以直观的看出它的值. 故而数字之中,每隔三位添加一个逗号,即千位分隔符.

var str = "1234567890";
(+str).toLocaleString();//"1,234,567,890"

如上,toLocaleString() 返回当前对象的”本地化”字符串形式.

  • 如果该对象是Number类型,那么将返回该数值的按照特定符号分割的字符串形式.
  • 如果该对象是Array类型,那么先将数组中的每项转化为字符串,然后将这些字符串以指定分隔符连接起来并返回.

toLocaleString 方法特殊,有本地化特性,对于天朝,默认的分隔符是英文逗号. 因此使用它恰好可以将数值转化为千位分隔符形式的字符串. 如果考虑到国际化,以上方法就有可能会失效了.

我们尝试使用环视来处理下.

function thousand(str){
  return str.replace(/(?!^)(?=([0-9]{3})+$)/g,',');
}
console.log(thousand(str));//"1,890"
console.log(thousand("123456"));//"123,456"
console.log(thousand("1234567879876543210"));//"1,879,876,543,210"

上述使用到的正则分为两块. (?!^) 和 (?=([0-9]{3})+$). 我们先来看后面的部分,然后逐步分析之.

"[0-9]{3}" 表示连续3位数字.
"([0-9]{3})+" 表示连续3位数字至少出现一次或更多次.
"([0-9]{3})+$" 表示连续3的正整数倍的数字,直到字符串末尾.
那么 (?=([0-9]{3})+$) 就表示匹配一个零宽度的位置,并且从这个位置到字符串末尾,中间拥有3的正整数倍的数字.
正则表达式使用全局匹配g,表示匹配到一个位置后,它会继续匹配,直至匹配不到.
将这个位置替换为逗号,实际上就是每3位数字添加一个逗号.

当然对于字符串"123456"这种刚好拥有3的正整数倍的数字的,当然不能在1前面添加逗号. 那么使用 (?!^) 就指定了这个替换的位置不能为起始位置.

千位分隔符实例,展示了环视的强大,一步到位.

正则实现模板引擎

var tmp = "An ${a} a ${b} keeps the ${c} away";
var obj = {
  a:"apple",b:"day",c:"doctor"
};
function tmpl(t,o){
  return t.replace(/\${(.)}/g,function(m,p){
    console.log('m:'+m+' p:'+p);
    return o[p];
  });
}
tmpl(tmp,obj);

//结果:
m:${a} p:a
m:${b} p:b
m:${c} p:c
"An apple a day keeps the doctor away"

正则表达式在H5中的应用

H5中新增了 pattern 属性,规定了用于验证输入字段的模式,pattern的模式匹配支持正则表达式的书写方式. 默认 pattern 属性是全部匹配,即无论正则表达式中有无 “^”,“$” 元字符,它都是匹配所有文本.

注: pattern 适用于以下 input 类型:text,search,url,telephone,email 以及 password. 如果需要取消表单验证,在form标签上增加 novalidate 属性即可.

回文字符串

function huiwen(str){
        str = str.replace(/[\W\s_]/gi,'');
        return str.toLowerCase().split('').reverse().join('') == str.toLowerCase();
    }

    console.log(huiwen(' a bdba'));
    console.log(huiwen(' a bdbc'));

trim去除首尾空格

function _trim(str){
    return str.replace(/^\s+|\s+$/,'');
}
//测试
console.log(_trim(' a bdba'));
 console.log(_trim('ia bdba'));

将所有单词首字母大写

name = 'aaa bbb ccc';
uw=name.replace(/\b\w+\b/g,function(word){
  return word.substring(0,1).toUpperCase()+word.substring(1);}
);

颜色字符串转换

将 rgb 颜色字符串转换为十六进制的形式,如 rgb(255,255,255) 转为 #ffffff
1. rgb 中每个,后面的空格数量不固定
2. 十六进制表达式使用六位小写字母
3. 如果输入不符合 rgb 格式,返回原始输入
示例1
输入: ‘rgb(255,255)’ 输出: #ffffff

function rgb2hex(sRGB) {
    return sRGB.replace(/^rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)$/,function($0,$1,$2,$3){
        return '#'+toHex($1)+toHex($2)+toHex($3);
    });
}
function toHex(str){
    return ('0'+(+str).toString(16)).slice(-2);
}

将字符串转换为驼峰格式

css 中经常有类似 background-image 这种通过 - 连接的字符,通过 javascript 设置样式的时候需要将这种样式转换成 backgroundImage 驼峰格式,请完成此转换功能
1. 以 - 为分隔符,将第二个起的非空单词首字母转为大写
2. -webkit-border-image 转换后的结果为 webkitBorderImage
示例1
输入 ‘font-size’
输出 fontSize

function cssStyle2DomStyle(sName) {
    //使用正则将 前一位有-的字符替换为大写【-([a-z])】 
    //replace第二个参数为函数时:
    //函数的第一个参数是匹配模式的字符 【t】
    //接下来的参数是与模式中的子表达式匹配的字符,可以有0个或多个这样的参数。【m】
    //接下来的参数是一个整数,代表匹配在被替换字符中出现的位置【i】
    //最后一个参数是被替换字符本身【这里没有用到】
    return sName.replace(/-([a-z])/g,function(t,m,i){return i?m.toUpperCase():m;})
}

将Doe,John替换为John,Doe

var name = "Doe,John";
var n = name.replace(/(\w+)\s*,\s*(\w+)/,"$2,$1");
//John,Doe

img标签匹配

var str = `<img src="http://image.163.com"></img>`;
var reg=`<img src="[."]*"></img>`;
var reg1=`<img src="[^"]*></img>`; 
var reg2=`<img src="[^"]*"></img>`; //正确
var reg3=`<img src="[."]*></img>`;
var arr = str.match(new RegExp(reg2));
console.log(arr);
console.log(arr.length);

参考链接:

完整的正则表达式:
http://www.jb51.cc/article/p-seawhpfj-d.html
exec、test等方法介绍:
http://www.jianshu.com/p/fcb648efc3de

相关文章

jquery.validate使用攻略(表单校验) 目录 jquery.validate...
/\s+/g和/\s/g的区别 正则表达式/\s+/g...
自整理几个jquery.Validate验证正则: 1. 只能输入数字和字母...
this.optional(element)的用法 this.optional(element)是jqu...
jQuery.validate 表单动态验证 实际上jQuery.validate提供了...
自定义验证之这能输入数字(包括小数 负数 ) &lt;script ...