`

18、javascript正则表达式详解(一)

阅读更多

创建正则式

//1、创建RegExp对象 
var regExp = new RegExp('cat','img'); 
//2、字面量方式 
regExp = /cat/img; 

正则表达式特殊字符

在正则表达式中,许多标点符号都有特殊的含义,它们是:

! $ ^ & * ( ) + { } | : ? - = [ ] . / \ 

 

/*
  正则表达式中的特殊字符
*/
function validation(){
	//键盘上的32个特殊字符
	var str = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;\',./";
	//!$^&*()+{}|:?-=[]./\ 为20个正则表达式特殊字符
	
	//----------是否包含某些字符
	//第一种:正则式对象,此情况下 ^&-[] 这5个在中括号里属于特殊字符
	var regRegExp = new RegExp('[~!@#$%\\^\\&*()_+{}|:\"<>?`\\-=\\[\\]\\\\;\',./]','g');
	var matchStr = "~,!,@,#,$,%,^,&,*,(,),_,+,{,},|,:,\",<,>,?,`,-,=,[,],\\,;,\',,,.,/";
	//~,!,@,#,$,%,^,&,*,(,),_,+,{,},|,:,",<,>,?,`,-,=,[,],\,;,',,,.,/
	alert(str.match(regRegExp).toString()==matchStr);//true

	//第二种:正则式常量,此情况下  ^&-[]/ 这6个是特殊字符
        var regStr =/[~!@#$%\^\&*()_+{}|:\"<>?`\-=\[\]\\;\',.\/]/g;
	//~,!,@,#,$,%,^,&,*,(,),_,+,{,},|,:,",<,>,?,`,-,=,[,],\,;,',,,.,/
	alert(str.match(regStr).toString()==matchStr);//true

	//----------是否与某个正则式完全匹配   
	// !$^&*()+{}|:?-=[]. 此种情况下这18个是特殊字符
	regRegExp = new RegExp('^~\\!@#\\$%\\^\\&\\*\\(\\)_\\+\\{\\}\\|\\:\"<>\\?`\\-\\=\\[\\]\\\\;\',\\./$','g');
	alert(regRegExp.test(str));//true
	// !$^&*()+{}|:?-=[]./ 此种情况下这19个是特殊字符
	var regStr1 =/^~\!@#\$%\^\&\*\(\)_\+\{\}\|\:\"<>\?`\-\=\[\]\\;\',\.\/$/g;   
	alert(regStr1.test(str));//true

	//str.match(regRegExp):用字符串去匹配正则式,返回满足正则式的数组   
	//regRegExp.test(str):以正则式去匹配字符串,返回布尔值
}

预定义类

代码

等同于

匹配

.

[^\n\r]

除了换行和回车之外的任意字符

\d

[0-9]

数字

\D

[^0-9]

非数字字符

\s

[ \t\n\x0B\f\r]

空白字符

\S

[^ \t\n\x0B\f\r]

非空白字符

\w

[a-zA-Z_0-9]

单词字符(所有的字母、数字和下划线)

\W

[^a-zA-Z_0-9]

非单词字符

量词

代码

描述

?

出现零次或一次

*

出现零次或多次(任意次)

+

出现一次或多次(至少出现一次)

{n}

一定出现 n

{n,m}

至少出现 n 次但不超过 m

{n,}

至少出现 n

贪婪的、惰性的(非贪婪的)(?)、支配性(+)的量词

贪婪量词 先看整个的字符串是否匹配。如果没有发现匹配,它去掉该字符串中的最后一个字符,并再次尝试。如果还是没有发现匹配,那么再次去掉最后一个字符,这个过程会一直重复直到发现一个匹配或者字符串不剩任何字符。即匹配时是从整个字符串的最后往前一次次尝试匹配。上面的量词表里的都是贪婪的。

 
惰性量词 先看字符串中的第一个字母是否匹配。如果单独这一个字符还不够,就读入下一字符,组成两个字符的字符串。如果还是没有发现匹配,惰性量词继续从字符串中添加字符直到发现匹配或者整个字符串都检查过也没有匹配。惰性量词和贪婪量词的工作方式恰好相反。即匹配时是从整个字符串的头向后一次次尝试匹配。


支配量词 只尝试匹配整个字符串。如果整个字符串不能产生匹配,不做进一步尝试。支配量词其实简单的说,就是一刀切。

贪婪

非贪婪

支配

描述

?

??

? +

出现零次或一次

*

*?

*+

出现零次或多次(任意次)

+

+?

++

出现一次或多次(至少出现一次)

{n}

{n}?

{n}+

一定出现 n

{n,m}

{n,m}?

{n,m}+

至少出现 n 次但不超过 m

{n,}

{n,}?

{n,}+

至少出现 n

 

分析下面三种量词区别:

var sToMatch = 'abbbaabbb12'; 
var re1 = /.*bbb/g; //贪婪 匹配:abbbaabbb 
var re2 = /.*?bbb/g; //非贪婪 匹配:abbb aabbb 
var re3 = /.*+bbb/g; //支配性 

 

第一个表达式re1,是贪婪,所以它首先看整个字符串,下面是整个匹配过程:

re1.test('abbbaabbb12');//false - no match 
re1.test('abbbaabbb1');//false - no match 
re1.test('abbbaabbb');//true – match 

 
所以re1返回的唯一结果是'abbbaabbb'。记住,点代表的是任意字符,b也包括在内,因此'abbbaa'匹配表达式.*的部分,'bbb'匹配表达式中bbb部分。


对于第二个表达式re2,匹配过程如下:

re2.test('a');//false - no match 
re2.test('ab');//false - no match 
re2.test('abb');//false - no match 
re2.test('abbb');//true - match 
//store this result and start with next letter 

re2.test('a');//false - no match 
re2.test('aa');//false - no match 
re2.test('aab');//false - no match 
re2.test('aabb');//false - no match 
re2.test('aabbb');//true - match 
//store this result and start with next letter 

re2.test('1');//false - no match 
re2.test('12');//false - no match 
//done 

 由于re2包含一个惰性量词,所以它能按照预期返回'abbb','aabbb'。

 
最后一个正则表达式re3,其实没有返回结果,因为它是支配性的,匹配过程如下:

re3.test('abbbaabbb12');//false - no match 

 
因为支配量词仅做一次测试 ,如果这个测试失败,就得不到结果。在这里,字符串结尾是'12',这导致表达式不能匹配。如果字符串是'abbbaabbb',那么re3就与re1返回相同结果。
注:浏览器对支配量词支持还很不完善 ,IE和Opera不支持支配量词,如果使用它,会抛错误。Mozilla不会产生错误,但是它会将支配量词看作是贪婪的。

 

另一贪婪示例:

//*、+和?限定符都称之为贪婪的,也就是说,他们尽可能多地匹配文字,
//如要匹配HTML标记的正则表达式,搜索所有的标签名
function searchHTMLTag(){
	//全文查找HTML元素的标签名,需要使用非贪婪准则
	var regStr =new RegExp("(<([^/]+?)>)","g");
	var HTMLStr1="<a>http://www.baidu.com</a><h1>http://www.baidu.com</h1>"

	var tmpArr = regStr.exec(HTMLStr1);

	//循环搜索
	while(tmpArr != null){
		//$1:整体匹配 $2:子匹配项
		alert(RegExp.$1 + "  " + RegExp.$2);
		//RegExp.lastIndex:最后一次成功匹配串的结束位置下一起始位置
		HTMLStr1 = HTMLStr1.slice(regStr.lastIndex);
		tmpArr = regStr.exec(HTMLStr1);
	}
}
searchHTMLTag();

反向引用

表达式计算完后,每个分组都被存放在一个特殊的地方以备将来使用。这些存储在分组中的特殊值,我们称之为反向引用。
反向引用可以有几种不同的使用方法:
第一 ,使用正则表达式对象的exec()、test()、match()、或search()方法后,反向引用值可以从RegExp构造函数中 获得。例如:

var regexp = /^(\d{2})+$/; 
var str = '123456'; 
regexp.test(str) ; 
alert('RegExp.$1 = ' + RegExp.$1);//56 

 

在调用test()方法后,所有的反向引用都被保存在RegExp构造函数中,从RegExp.$1 (它保存了第一个反向引用)开始,如果还有第二个反向引用,就是RegExp.$2,依此类推。因为上面只有一组,且最后匹配的是56,所以RegExp.$1就存储了此串。


第二 ,可以直接在定义分组的表达式中包含反向引用 。这可以通过使用特殊转义序列如\1、\2 来实现。例如:

var sToMatch = 'dogdog'; 
var reNumbers = /(dog)\1/; 
alert(reDogDog.test(sToMatch));//true 

 
正则表达式reDogDog首先创建单词dog的组,然后又被特殊转义序列\1引用,使得这个正则表达式等同于/dogdog/。


第三 ,反向引用可以用在String对象的replace()方法中 ,这通过使用特殊字符序列$1、$2 等等来实现。例如:

var sToMatch = '1234 5678'; 
var reMatch = /(\d{4}) (\d{4})/; 
var sNew = sToMatch.replace(reMatch,'$2 $1'); 
alert(sNew);//5678 1234

成对串(标签名、单引号、双引号)匹配示例:

function testReverseRef(){    
    var reg = /<(\w+)\s*(\w+(=('|").*?\4)?\s*)*>.*?<\/\1>/g;    
    var str = '<td id=\'td1\' style=\"bgcolor:white\"></td>';    
    //<td id=\'td1\' style=\"bgcolor:white\"></td>
    alert(str.match(reg));
}

非捕获性分组(?:pattern )

创建反向引用的分组,我们称之为捕获性分组 。同时还有一种非捕获性分组 ,它不会创建返向引用 。在较长的正则表达式中,存储返向引用会降低匹配速度。通过使用非捕获性分组,仍然可以拥有与匹配字符串序列同样的能力,而无需存储结果里的开销。

 

注意:正(?=pattern)/负(?!pattern)向搜索也是属于非捕获性分组


如果要创建一个非捕获性分组,只要在左括号的后面加上一个问号和一个紧跟的冒号:

var sToMatch = '#123456789'; 
var reNumbers = /#(?:\d+)/; 
reNumbers.test(sToMatch); 
alert(RegExp.$1);//输出结果为空字符串

 
正因如此,replace()方法就不能RegExp.$x变量来使用任何反向引用,或在正则表达式中使用它。请看:

var sToMatch = '#123456789'; 
var reNumbers = /#(?:\d+)/; 
alert(sToMatch.replace(reNumbers,'abcd$1'));//abcd$1 

 
上面代码输出'abcd$1'而不是'abcd123456789',因为'$1'在这里并不被看成是一个反向引用,而是被直接翻译成字符。
再看一个去HTML页面文本域中的恶意的HTML标签:

String.prototype.stripHTML = function () {
	var regexp = / <(?:.|\s)*?>/g;
	return this.replace(regexp, "");
};
alert(" <b>This would be bold </b>".stripHTML());//This would be bold 

前预搜索(?=pattern )(?!pattern )

正则表达式运算器向前看一些字符而不移动其位置 ,可分为“正向前预搜索(?=pattern)”与“负向前预搜索(?!pattern)”,“正向前预搜索”检查的是接下来出现的是不是某个特定字符集 。而“负向前预搜索”则是检查接下来的不应该出现的特定字符集
创建正向前预搜索要将模式放在(?=和)之间,注意,这不是分组,它们也是非捕获组(无论是正向还是负向)。

var sToMatch1 = 'bedroom'; 
var sToMatch2 = 'bedding'; 
var reBed = /(bed(?=room)((\w)+))/; //表示只匹配后面跟room的bed,所以匹配'bedroom' 
alert(reBed.test(sToMatch1));//true 
alert(RegExp.$1);//bedroom 
alert(RegExp.$2);//room 
alert(RegExp.$3);//m 
alert(RegExp.$4);//空 上面虽然有4个子表达式,但由于第2为非捕获,所以只存储三组 
//注:正则式为非全局,这里还是从头开始匹配sToMatch2 
alert(reBed.test(sToMatch2));//false 

 

创建负向前预搜索要将模式放在(?!和)之间:

var sToMatch1 = 'bedroom'; 
var sToMatch2 = 'bedding'; 
var reBed = /(bed(?!room)((\w)+))/;//表示只匹配后面不跟room的bed,所以匹配'bedding' 
alert(reBed.test(sToMatch1));//false 
alert(reBed.test(sToMatch2));//true 
alert(RegExp.$1);//bedding 
alert(RegExp.$2);//ding 
alert(RegExp.$3);//g 
alert(RegExp.$4);//空 

示例:

function testBeforehandSearch(){    
    var reg = /(\w)((?=\1\1\1)(\1))+/g;    
    var str = "aaa ffffff 999999999";
    //ffff 9999999
    alert(str.match(reg));    
    
    //先取一个,再下一匹配位置进行预搜索。
    reg = /(.(?!\bstop\b))+/g;    
    var str = "fdjka ljfdl stop fjdsla fdj";
    //'fdjka ljfdl','stop fjdsla fdj'
    alert(str.match(reg));
    
    //先进行预搜索,再取预搜索中的第一个字符。
    reg = /((?!\bstop\b).)+/g;    
    var str = "fdjka ljfdl stop fjdsla fdj";
    //'fdjka ljfdl ','top fjdsla fdj'
    alert(str.match(reg));    
}

后搜索(?<=pattern )(?<!pattern )

注:尽管JavaScript支持正则表达式前预搜索,但它不支持后预搜索。后预搜索可以匹配这种模式:“匹配B当且仅当它前面有或没有A”。

未来示例(现还不支持,将来应该可以用^-^):

//后搜索,但javascript还不支持,Java支持
function testAfterhandSearch(){        
    var reg = /(?<=\d{4})\d+(?=\d{4})/;    
    var str = "1234567890123456";
    //56789012
    alert(str.match(reg));    
}

字符边界

边界

描述

^

行开头,与字符串开始的地方匹配,不匹配任何字符

$

行结尾,与字符串结束的地方匹配,不匹配任何字符

\b

单词的边界

\B

非单词的边界

 

有4种位置被认为是“单词边界”:

  1. 在字符串的第一个字符前的位置(如果字符串的第一个字符是一个“单词字符”)
  2. 在字符串的最后一个字符后的位置(如果字符串的最后一个字符是一个“单词字符”)
  3. 在一个“单词字符”和“非单词字符”之间,其中“非单词字符”紧跟在“单词字符”之后
  4. 在一个“非单词字符”和“单词字符”之间,其中“单词字符”紧跟在“非单词字符”后面

 “单词边界”的取反集为\B,他要匹配的位置是两个“单词字符”之间或者两个“非单词字符”之间的位置。

 

提取单词:

var sToMatch= "first second third"; 
var reg = /\b(\S+?)\b/g; 
alert(sToMatch.match(reg));//first,second,third 
function testCharSpace(){
    var str = '@@@abc';
    alert(str.match(/.\b./g));//@a
    
    str =  "weekend,endfor,end";
    alert(str.match(/\bend\b/g));//匹配最后一个end
    
    str =  "weekend,endfor,end";
    //注:/\B.*\B/可以匹配两个单词字符之间的间隔
    var arr = str.match(/\B.*\B/g);
	alert(arr[0]);//eekend,endfor,en
    //空,第一次最大匹配后匹配位置落在了最后一个d字母前
    //,所以最后匹配上了d字母前的间隔
    alert(arr[1]);
    
    str =  ",endfor,";
    var arr = str.match(/\B.*\B/g);
    alert(arr[0]);//,endfor,
    alert(arr[1]);//空
    
    str =  "";
    var arr = str.match(/\B.*\B/g);
    alert(arr[0]);//空
}
//testCharSpace();

多行模式

问题出现:

var sToMatch= "first 1\nsecond 2\nthird 3"; 
var reg = /(\w+)$/g; 
alert(sToMatch.match(reg));//3 

 
$匹配的是行尾,在sToMatch中有两个换行符,所以1 2也应该返回,因为它们都是不同行的行尾。这就需要用到多行模式,即把字符看成多行处理,而不是一行。
要指定多行模式,只要在正则表达式后面添加一个m选项。这会让$边界匹配换行符(\n)以及字符串真正的结尾。为上面正则加上m选项:

var sToMatch= "first 1\nsecond 2\nthird 3"; 
var reg = /(\w+)$/gm; 
alert(sToMatch.match(reg));//1,2,3 

 
多行模式同样也会改变^边界的行为,如果是多行文本,则要加上m选项:

var sToMatch= "first 1\nsecond 2\nthird 3"; 
var reg = /^(\w+)/mg; 
alert(sToMatch.match(reg));//first,second,third

理解RegExp对象

RegExp实例与构造函数都有属性,两者的属性在创建模式和进行测试的时候都会发生改变。

实例属性

global ——RegExp 对象是否具有标志 g。
ignoreCase ——RegExp 对象是否具有标志 i。
lastIndex ——一个整数,标示开始下一次匹配的字符位置(只有当使用正则试的exec()或test()函数 才会设置,否则为0) (注,IE上的String对象的match()方法也会影响,但Firefox上不会影响该正则式对象属性)
multiline ——RegExp 对象是否具有标志 m。
source ——正则表达式的源文本。

 

var reg = /^(\w+)/img; 
alert(reg.global);//true 
alert(reg.ignoreCase);//true 
alert(reg.multiline);//true 
alert(reg.source);//^(\w+) 

 
上面这些属性没有什么太大用处,因为在测试前都是已知的。真正有用的属性是lastIndex,它告诉你正则表达式在某个字符串中停止之前,查找了多远

var sToMatch = 'bbq is short for barbecue'; 
var reg = /b/g; 
reg.exec(sToMatch); 
alert(reg.lastIndex)//1 
reg.exec(sToMatch); 
alert(reg.lastIndex)//2 
reg.exec(sToMatch); 
alert(reg.lastIndex)//18 
reg.exec(sToMatch); 
alert(reg.lastIndex)//21 

如果想从头开始匹配,可以将lastIndex设为0

var sToMatch = 'bbq is short for barbecue'; 
var reg = /b/g; 
reg.exec(sToMatch); 
alert(reg.lastIndex)//1 
reg.lastIndex = 0; 
reg.exec(sToMatch); 
alert(reg.lastIndex)//1 

静态属性

静态的RegExp属性对所有的正则表达式都有效。它们都有两个名字:一个复杂名字和一个以美元符号开头简短的名字。下表中列出了这些属性: 

 

 

长名

短名

描述

input

$_

最后用于匹配的字符串(传递给exec()test() 的字符串)

lastMatch

$&

最后匹配的字符

lastParen

$+

最后匹配的分组

leftContext

$`

在上次匹配的前面的子串

multiline

$*

用于指定是否所有的表达式都使用多行模式的布尔值

rightContext

$'

在上次匹配之后的子串

$1...9

$1...9

执行regExp.test()regExp.exec()str.match() 后,存储各子表达式所匹配的项的值

   
使用exec()或test()完成匹配后,RegExp会存储一些信息:

var sToMatch = 'this has been a short, short summer'; 
var reg = /(s)hort/g; 
reg.test(sToMatch); 
alert(RegExp.input);//this has been a short, short summer 
alert(RegExp.leftContext);//this has been a 
alert(RegExp.rightContext);//, short summer 
alert(RegExp.lastMatch);//short 
alert(RegExp.lastParen);//s 
alert(RegExp.$1);//s
alert(RegExp.$2);//空 注,不是没有而是空

 

使用短名重写上面代码:

//... 
alert(RegExp.$_);//this has been a short, short summer 
alert(RegExp['$`']);//this has been a 
alert(RegExp["$'"]);//, short summer 
alert(RegExp['$&']);//short 
alert(RegExp['$+']);//s
alert(RegExp.$1);//s
alert(RegExp.$2);//空

 

注意:每次执行exec()或test()时,RegExp所有的静态属性(除multiline外)都会被重新设置。RegExp.multiline设置是全局的,只要设置了就会影响所有正则式。但IE和Opera并不支持RegExp.multiline,所以最好单独对每个表达式设置m选项而不要直接设置这个标记。

 

 另外:创建正则式后应用之前,这些静态属性都会清空成空字符串

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics