JavaScript正则表达式RegExp

发布于 2016-11-02 17:07 阅读数 387

本文必须得到作者授权后,方可转载,摘要引流随意。
By 依韵 , From https://blog.cdswyda.com/post/20161102

正则表达式,也称规则表达式,经常使用其来完成对字符串的校验和过滤。由于正则表达式的灵活性、逻辑性和功能性都非常强大,而且 可以利用很简单的方式完成对复杂字符串的控制,所以很多程序语言都支持正则表达式。在JavaScript中正则表示也非常强大和实用。

基本形式

正则表达式(regular expression)是一种表达文本模式(即字符串结构)的方法,有点像字符串的模板,常常用作按照“给定模式”匹配文本的工具。比如,正则表达式给出一个Email地址的模式,然后用它来确定一个字符串是否为Email地址。JavaScript的正则表达式体系是参照Perl 5建立的。

新建正则表达式有两种方法。一种是使用字面量,以斜杠表示开始和结束。

1
2
3
4
// 字面量形式
var telRegex1 = /^1[3|5|7|8]\d{9}$/;
// 构造函数形式
var telRegex2 = new RegExp('^1[3|5|7|8]\\d{9}$');

以上都是创建了一个内容为^1[3|5|7|8]\d{9}$的正则表达式,其表示对一个手机号码的校验。必须以1开始,第二位为3/5/7/8,之后为9位数字。

这两种写法——字面量和构造函数——在运行时有一个细微的区别。采用字面量的写法,正则对象在代码载入时(即编译时)生成;采用构造函数的方法,正则对象在代码运行时生成。考虑到书写的便利和直观,实际应用中,基本上都采用字面量的写法。

有一点需要注意,使用构造函数创建正则表达式时,传入的参数是字符串形式的,在字符串内部,\本身也是一个转义符,因此需要再使用一个\来对其进行正则表达式的转义。上面第二个示例中,\\d才能代表任意数字。

关于正则表达式中,各种符号的含义,以及使用方法,请看后面的介绍:

元字符

一些常用的元字符如下:

  • . 匹配除换行符之外的任意字符

  • \w 匹配字母或数字或下划线或汉字

  • \s 匹配任意的空白符

  • \d 匹配数字

  • \b 匹配单词的开始或结束

  • ^ 匹配字符串的开始处

  • $ 匹配字符串的结束处。

  • * 匹配前面的子表达式任意次。

  • ? 匹配前面子表达式0次或一次,等价于{0, 1}

  • + 匹配之前子表达式一次到多次,等价于{1, }

  • {n} 匹配之前的子表达式n次。

  • {m,n} 匹配之前的子表达式最少m次,最多n次。

  • {n, } 匹配之前的子表达式至少n次。

  • [xyz] 字符集合,表示其中任意一个字符。表示范围可用-链接,例如[a-z] 表示a-z之间的任意一个字母。还可以这样书写[A-Za-z0-9]

  • [^xyz] 字符即可,表示非其中任意一个字符。表示范围可用-链接,例如[^a-z] 表示非 a-z之间的任意一个字母。

  • | 表示或(or)关系,例如 com|cn,表示匹配com或者cn。

  • () 用于分组,其分组中的内容可已通过$1-$9按顺序获取(字符串相关方法中),之后的正则中也可以通过\1-\9进行引用(正则表达式内部)。(分组0表示整个正则匹配内容或整个正则表达式)

在正则表达式中,以上这些以及一些未列出的元字符都是有自身含义的,如果我们需要匹配这些元字符本身,可以使用\对其进行转义即可。

更多元字符可以查看:正则表达式

属性

修饰符

  • ignoreCase:返回一个布尔值,表示是否设置了i修饰符,该属性只读。

  • global:返回一个布尔值,表示是否设置了g修饰符,该属性只读。

  • multiline:返回一个布尔值,表示是否设置了m修饰符,该属性只读。

  • stickyES6返回一个布尔值,表示是否设置了y修饰符,只读。

1
2
3
4
5
var r = /abc/igm;

r.ignoreCase; // true
r.global;  // true
r.multiline;  // true

匹配时属性

  • lastIndex:返回下一次开始搜索的位置。该属性可读写,但是只在设置了g修饰符时有意义。

  • sourceES5返回正则表达式的字符串形式(不包括反斜杠),该属性只读。

  • flagsES6返回正则表达式中的修饰符。

1
2
3
4
5
var r = /abc/igm;

r.lastIndex; // 0
r.source; // "abc"
r.flags; //"igm"

方法

test()

正则对象的test对象接收一个字符串,表示测试字符串,返回一个布尔值,表示是此字符串是否满足匹配条件。

1
2
3
telRegex1.test('13612341234'); // true
telRegex2.test('13612341234'); // true
telRegex1.test('136123412'); // false

如果正则表达式带有g修饰符,则每一次test方法都从上一次结束的位置开始向后匹配。同时,可以通过正则对象的lastIndex属性指定开始搜索的位置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var xReg = /x/g;
var str = 'xyz_x1_y1_x3';

xReg.lastIndex; // 0
xReg.test(str); // true

xReg.lastIndex; // 1
xReg.test(str); // true
xReg.lastIndex; // 5

// 指定位置开始 指定下次匹配从最后一位开始,就匹配不到了
xReg.lastIndex = 11; // 11
xReg.test(str); // false

xReg.lastIndex; // 0
1
var indexReg = /^(?:http|https).+\/jwebui\/pages\/themes\/(\w+)\/\1\.jspx(\?\S+)?$/i ;

上面是一个F8中检查是否为首页的正则表达式。

  • 最开始的^ 和最后的$分别表示匹配的开始和结束。

  • (?:http|https)表示两者之一,这么写是非获取的组匹配,()不会被分组存储。也可以写成(http|https) 但是后面的\1就需要替换成\2了,因为这么写时此处形成了第一个分组。

  • .+ 就是任意字符至少出现一次。

  • \/jwebui\/pages\/themes\/ 就是匹配字符串"/jwebui/pages/themes/"

  • (\w+) 作为第一个分组,表示任意字母或数字或下划线或汉字至少出现一次。

  • \1表示对第一个分组的引用,再重复第一分组的内容 。

  • \.jspx 表示.jspx

  • (\?\S+)? 表示(\?\S+) 匹配的内容出现0次或一次。其中:

    • \? 表示

    • \S+ 表示任意可见字符出现至少一次。

`

exec()

正则对象的exec方法,可以返回匹配结果。如果发现匹配,就返回一个数组,成员是每一个匹配成功的子字符串,否则返回null

如果正则表示式包含圆括号(即含有“组匹配”),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号,第三个成员对应第二个括号,以此类推。整个数组的length属性等于组匹配的数量再加1。

1
2
3
4
var ipReg = /(\d{1,3}\.){3}(\d{1,3})/;
var ipStr = 'My ip is "192.168.118.47" , please tell me yours';

ipReg.exec(ipStr); // ["192.168.118.47", "118.", "47"]

上面第一段代码表示一个简单的IP检验,数字的1-3位之后紧跟一个.,接着这个整体要出现3次,最后再有一段数字的1-3位。结果数组中,第一个值表示匹配到的结果,之后的表示正则分组匹配到的内容。

如果正则表达式加上g修饰符,则可以使用多次exec方法,下一次搜索的位置从上一次匹配成功结束的位置开始。同时还可以指定lastIndex,使之下次从指定位置开始(可见之前的test示例)。

1
2
3
4
5
6
7
var ipLastReg = /\d+(?=;)/g;

var ipsStr = '192.168.118.47;192.168.118.46;192.168.118.48;';

ipLastReg.exec(ipsStr); // ["47"]
ipLastReg.exec(ipsStr); // ["46"]
ipLastReg.exec(ipsStr); // ["48"]

上面代码中正则中的(?=;)表示先行断言,表示只匹配在;前面\d+

如果只是为了得到是否匹配,请使用RegExp.test()方法或字符串实例的.search()替代,效率更高。

字符串相关方法

之所以称之为字符串相关方法是因为其是在字符串上调用的(虽然ES6开始,内部调用的是正则上的方法,但还是在字符串上提供的入口)。

  • match():返回一个数组,成员是所有匹配的子字符串。

  • search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。

  • replace():按照给定的正则表达式进行替换,返回替换后的字符串。

  • split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。

match()

match方法对字符串进行正则匹配,返回匹配结果。此方法方法与正则对象的exec方法非常类似:匹配成功返回一个数组,匹配失败返回null。如果正则表达式带有g修饰符,则该方法与正则对象的exec方法行为不同,会一次性返回所有匹配成功的结果。

1
2
3
4
var ipLastReg = /\d+(?=;)/g;
var ipsStr = '192.168.118.47;192.168.118.46;192.168.118.48;';

ipsStr.match(ipLastReg); // ["47", "46", "48"]

上面的正则是匹配IP中的最后一位,其中使用了(?=;)意为先行断言,表示只匹配在;之前的内容,但是不包括;。关于更多先行断言,请看下文。

search()

search方法,返回第一个满足条件的匹配结果(可直接使用字符串,不一定是正则对象)在整个字符串中的位置。如果没有任何匹配,则返回-1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var nowDateStr = '2016-11-1';
var testReg = /-/g;

nowDateStr.search(testReg); // 4
// 再次查找还是4
nowDateStr.search(testReg); // 4

//  检查lastIndex 并设置 
testReg.lastIndex; // 0
testReg.lastIndex = 6;
nowDateStr.search(testReg); // 4  结果仍为4

search方法总是从字符串的开始位置查找,与正则表达式的g修饰符和lastIndex属性无关。

replace()

replace方法可以替换匹配的值,返回替换后的新字符串。它接受两个参数,第一个是搜索模式(可直接使用字符串,不一定是正则对象),第二个是替换的内容(可使用字符串或一个函数)。搜索模式如果不加g修饰符,就替换第一个匹配成功的值,否则替换所有匹配成功的值。

其中replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容,具体如下所示:

  • $& 指代匹配的子字符串。

  • $` 指代匹配结果前面的文本。

  • $' 指代匹配结果后面的文本。

  • $n 指代匹配成功的第n组内容,n是从1开始的自然数。

  • $$ 指代美元符号$。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var re = /-/g; 
var str = '2016-11-01';
var newstr = str.replace(re,'.');
console.log(newstr);  // "2016.11.01"

'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1');
// "world hello"

'abc'.replace('b', '[$`-$&-$\']');
// "a[a-b-c]c"

第二个参数为函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function toCamelStyle(str) {
    // 匹配-以及之后的一个字符,其中这个字符在一个分组内
    var camelRegExp = /-([a-z])/ig;

    return str.replace(camelRegExp, function(all, letter) {
        // all为匹配到的内容,letter为组匹配        
        return letter.toUpperCase();
    });
}

toCamelStyle('margin-left'); // "marginLeft"
toCamelStyle('aa-bb-cccc'); // "aaBbCccc"

以上代码展示通过正则将aa-bb-cccc这样的字符串转化为aaBbCccc 这种形式。replace回调函数接收两个参数,第一个为匹配到的内容,第二个为匹配到的分组,有多少组就可以传多少个参数,在此之后还可以有两个参数,一个为匹配到内容在原字符串的位置,另一个是原字符串。

split()

split方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。该方法接受两个参数,第一个参数是分隔规则(可直接使用字符串,不一定是正则对象),第二个参数是返回数组的最大成员数。

1
2
'2016-11-01'.split('-'); // ["2016", "11", "01"]
'2016-11-01'.split(/-/); // ["2016", "11", "01"]

贪婪模式和懒惰模式

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符,称之为贪婪模式

例如:

1
2
var s = 'aaa';
s.match(/a+/); // ["aaa"]

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。

1
2
var s = 'aaa';
s.match(/a+?/); // ["a"]

以下是一些说明

  • *? 重复任意次,但尽可能少重复

  • +? 重复1次或更多次,但尽可能少重复

  • ?? 重复0次或1次,但尽可能少重复

  • {n,m}? 重复n到m次,但尽可能少重复

  • {n,}? 重复n次以上,但尽可能少重复

也就是说默认情况下,都是贪婪模式,加上一个时就转化为了懒惰模式,也称非贪婪模式。

组匹配

通常一个()中的内容就构成了一个分组,此分组内容将被存储,可在之后的正则表达式(使用\1-\9)和相关方法中(使用$1-$9)引用,前面已经介绍过了,就不再说了。

关于组匹配,还有以下几种情况:

非捕获组

(?:x) 称为非捕获组(Non-capturing group),表示不返回该组匹配的内容,即匹配的结果中不计入这个括号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 正常匹配
var url = /(http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;

url.exec('http://google.com/');
// ["http://google.com/", "http", "google.com", "/"]

// 非捕获组匹配
var url = /(?:http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;

url.exec('http://google.com/');
// ["http://google.com/", "google.com", "/"]

之后先行断言先行否定断言也都是非捕获组

先行断言

x(?=y)称为先行断言(Positive look-ahead),x只有在y前面才匹配,y不会被计入返回结果。

比如之前匹配ip的例子:

1
2
3
4
var ipLastReg = /\d+(?=;)/g;
var ipsStr = '192.168.118.47;192.168.118.46;192.168.118.48;';

ipsStr.match(ipLastReg); // ["47", "46", "48"]

上面正则对象中(?=;)就表示只匹配在;之前的内容,但是不包括;

先行否定断言

x(?!y)称为先行否定断言(Negative look-ahead),x只有不在y前面才匹配,y不会被计入返回结果。

1
2
3
4
var xreg = /\d+(?!%)/g ;
xreg.exec('100% is 1'); // ["10"]
xreg.exec('100% is 1'); // ["1"]
/\d+?(?!%)/.exec('100% is 1'); // ["1"]

上面代码表示匹配不在%前的数字,xreg中直接书写的\d+ 表示贪婪模式,因此第一次匹配到的是10,第二次才会匹配到后面的1,因为作为数字10本身也不在%前面,正则不会将100当成一个整体(注意:这里需要定义一个正则对象来调用,直接以字面量形式的正则调用时,每次调用都是一个新对象,结果始终是10)。

为了一次匹配到最后的1,我们在\d+之后加一个?将其转为非贪婪模式即可。

为了一次匹配到前面100中的1,我们在\d+之后加一个?将其转为非贪婪模式即可。

ES6之前,JavaScript中不支持后行断言否定后行断言,ES6中添加了对此的支持,请看之后的ES扩展部分。

ES6扩展

构造函数

RegExp构造函数的参数有两种情况。

  • 第一种情况是,参数是字符串,这时第二个参数表示正则表达式的修饰符(flag)。
  • 第二种情况是,参数是一个正则表示式,此时不能有第二个参数,会返回一个原有正则表达式的拷贝。

ES6 针对第二种情况,允许传入第二个参数,用于设置第一个参数正则表达式的修饰符。

1
2
3
var regex = new RegExp(/xyz/, 'i'); // ES6之前 语法错误

new RegExp(/abc/ig, 'i'); // ES6中结果为: /abc/i  

字符串的正则方法

字符串对象共有4个方法,可以使用正则表达式:match()replace()search()split()

ES6将这4个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。

修饰符

ES6对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于\uFFFF的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。

ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。

y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

// 第一次都能正确匹配
r1.exec(s); // ["aaa"]
r2.exec(s); // ["aaa"]

// 第二次结果就不一致了
r1.exec(s); // ["aa"]
r2.exec(s); // null

个人理解,\y是类似于在每次匹配时隐式地添加了^,表示开始位置。

属性

ES5中,正则对象存在source属性,用于返回正则表达式本身。

ES6中,又添加了flags属性,用于返回正则对象的所有修饰符。

后行断言

后行断言于先行断言相反。例如/(?<=y)x/ 表示匹配x,但是要求x必须在y后面。

同理 后行否定断言则为:/(?<!=y)x/ 表示匹配x,但是要求x不能在y后面。

需要注意的是,存在后行断言时,正则执行顺序发生了改变,会先匹配后行断言的这部分,再匹配其他的的,顺序变成了从右向左。因此一些匹配操作的结果可能大不一致,而且正则中的\1-\9的引用顺序也会发生变化。

参考链接

错误修正

先行否定断言中

为了一次匹配到最后的1,我们在\d+之后加一个?将其转为非贪婪模式即可。

为了一次匹配到前面100中的1,我们在\d+之后加一个?将其转为非贪婪模式即可。

于 2017-03-24 21:24:25


Comments
Write a Comment