正则表达式是一个精巧的利器,经常用来在字符串中查找和替换,JavaScript 语言参照 Perl,也提供了正则表达式相关模块,开发当中非常实用,在一些类库或是框架中,比如 jQuery,就存在大量的正则表达式,所以说学好正则表达式,是提高开发技能的一项基本要求。那么今天博主就来详细总结一下正则表达式的相关知识,希望不熟悉的同学们,也能够掌握正则表达式的原理及应用。
在 JS 中,创建正则表达式有两种方式,一种是字面量方式,一种是构造器方式,如下所示:
- var regex = /\w+/;
- // 或者
- var regex = new RegExp('\\w+');
大家也许注意到,使用字面量要比构造器简洁得多,\w 表示一个 word,匹配单个字母、数字或下划线,而使用 RegExp 构造器时,我们的正则变为了 "\\w",这是因为要在字符串中表示一个反斜杠 \,我们需要对其转义,也就是在前面再加一个转义字符 \。相信大家都知道,要在字面量正则中表达一个匹配反斜杠 \ 的正则,只需写成 \\ 这样,但在字符串中表达这个正则,则是 "\\\\" 这个样子的,这是因为字符串中前两个表示一个反斜杠 \,后两个也表示一个反斜杠 \,最终在正则层面,结果还是 \\。
对于上面两种创建形式,都可以加上一些后缀修饰符,这些修饰符可以单个使用,也可以组合起来使用:
- /\w+/g; // global search
- / \w + /i; / / ignore
- case / \w + /m; / / multi - line / \w + /u; / / unicode / \w + /y; / / sticky / \w + /gi;
- new RegExp('\\w+', 'gi');/
从英文注释来看,相信大家都大概都略知一二了,需要注意的是 u 和 y 修饰符,它们是 ES6 新增的特性,u 表示启用 Unicode 模式,对于匹配中文特别有用,而 y 是 sticky,表示 "粘连",跟 g 很相似,都属于全局匹配,但它们也有不同之处,这个我们后面会介绍。
有了正则表达式对象了,如何使用呢?JS 中的正则和字符串在原型中均提供相应的方法,先来看看正则原型中的两个方法:
- RegExp.prototype.test(str);
- RegExp.prototype.exec(str);
上面的 test() 和 exec() 方法都需传入一个字符串,对这个字符串进行搜索和匹配,不同的是,test() 方法会返回 true 或 false,表示字符串和正则是否匹配,而 exec() 方法在匹配时返回一个匹配结果数组,如果不匹配,则只返回一个 null 值,下面来看看两者的差异:
- // RegExp#test()
- var regex = /hello/;
- var result = regex.test('hello world'); // true
- // RegExp#exec()
- var regex = /hello/;
- var result = regex.exec('hello world'); // ['hello']
对于 exec() 方法,如果正则中含有捕获组,匹配后则会出现在结果数组中:
- // (llo)是一个捕获组
- var regex = /he(llo)/;
- var result = regex.exec('hello world'); // ['hello', 'llo']
开发当中,test() 方法一般用于用户输入验证,比如邮箱验证,手机号验证等等,而 exec() 方法一般用于从特定内容中获取有价值的信息,比如从用户邮箱输入中获取其 ID 和邮箱类型,从手机号中获取此号码的归属地等等。
上面是正则原型中的两个方法,现在来看看字符串原型中都提供了哪些可用的方法:
- String.prototype.search(regexp);
- String.prototype.match(regexp);
- String.prototype.split([separator[, limit]]);
- String.prototype.replace(regexp | substr, newSubStr |
- function);
先来说说 String#search() 方法,它会根据正则参数对字符串进行匹配搜索,如果匹配成功,就返回第一次匹配处的索引,如果匹配失败,则返回 - 1。
- // String#search()
- 'hello world'.search(/hello/); // 0
- 'hello world'.search(/hi/); // -1
String#match() 方法跟 RegExp#exec() 方法相似,会返回结果数组,所不同的是,如果 String#match() 的正则参数中含有全局标记 g,则结果中会只出现匹配的子串,而忽略捕获组,这一点与 RegExp#exec() 有些出入。且看下面代码:
- // String#match()
- 'hello hello'.match(/he(llo)/); // ['hello', 'llo']
- // String#match()遇到全局g修饰符时会舍弃捕获组
- 'hello hello'.match(/he(llo)/g); // ['hello', 'hello']
- // RegExp#exec()仍旧包含捕获组
- / he(llo) / g.exec('hello hello'); // ['hello', 'llo']
所以,如果需要总是将捕获组作为结果返回,应该使用 RegExp#exec() 方法,而不是 String#match() 方法。
接下来说说 String#split() 方法,这个方法用于将字符串分割,然后返回一个包含其子串的数组结果,其中 separator 和 limit 参数都是可选的,separator 可指定为字符串或正则,limit 指定返回结果个数的最大限制。如果 separator 省略,该方法的数组结果中仅包含自身源字符串;如果 sparator 指定一个空字符串,则源字符串将被以字符为单位进行分割;如果 separator 是非空字符串或正则表达式,则该方法会以此参数为单位对源字符串进行分割处理。下面代码演示了该方法的使用:
- // String#split()
- 'hello'.split(); // ["hello"]
- 'hello'.split(''); // ["h", "e", "l", "l", "o"]
- 'hello'.split('', 3); // ["h", "e", "l"]
- // 指定一个非空字符串
- var source = 'hello world';
- var result = source.split(' '); // ["hello", "world"]
- // 或者使用正则表达式
- var result = source.split(/\s/); // ["hello", "world"]
如果 separtor 是一个正则表达式,并且正则中包含捕获组,则捕获组也会出现在结果数组中:
- // String#split() 正则捕获组
- var source = 'matchandsplit';
- var result = source.split('and'); // ["match", "split"]
- var result = source.split(/and/); // ["match", "split"]
- // 正则中含捕获组
- var result = source.split(/(and)/); // ["match", "and", "split"]
最后来介绍一下 String#replace() 方法,它会同时执行查找和替换两个操作。
从上面的函数签名来看,该方法会接受两个参数:第一个参数可以是一个正则表达式,也可以是一个字符串,它们都表示将要匹配的子串;第二个参数可以指定一个字符串或是一个函数,如果指定一个字符串,表示这个字符串将会替换掉已匹配到的子串,如果指定一个函数,则函数的返回值会替换掉已匹配的子串。
String#replace() 方法最终会返回一个新的已经过替换的字符串。下面分别演示了 replace 方法的使用:
- // String#replace()
- var source = 'matchandsplitandreplace';
- var result = source.replace('and', '-'); // "match-splitandreplace"
- // 或者
- var result = source.replace(/and/,
- function() {
- return '-';
- }); // "match-splitandreplace"
从上面的代码中可以看到,'and'被替换成了'-',但我们同时也注意到,只有第一个'and'被替换了,后面的并没有被处理。这里我们就需要了解,String#replace() 方法只对第一次出现的匹配串进行替换,如果我们需要全局替换,需要将第一个参数指定为正则表达式,并追加全局 g 修饰符,就像下面这样:
- // String#replace() 全局替换
- var source = 'matchandsplitandreplace';
- var result = source.replace(/and/g, '-'); // "match-split-replace"
- var result = source.replace(/and/g,
- function() {
- return '-';
- }); // "match-split-replace"
初学者看到上面的代码,可能会觉得疑惑,对于第二个参数,直接指定一个字符串也挺简单的嘛,我们为何要使用一个函数然后再返回一个值呢。我们看看下面的例子就知道了:
- // String#replace() 替换函数的参数列表
- var source = 'matchandsplitandreplace';
- var result = source.replace(/(a(nd))/g,
- function(match, p1, p2, offset, string) {
- console.group('match:');
- console.log(match, p1, p2, offset, string);
- console.groupEnd();
- return '-';
- }); // "match-split-replace"
上面代码中,第一个参数是正则表达式,其中包含了两个捕获组 (and) 和(nd),第二个参数指定一个匿名函数,其函数列表中有一些参数:match, p1, p2, offset, string,分别对应匹配到的子串、第一个捕获组、第二个捕获组、匹配子串在源字符串中的索引、源字符串,我们可以称这个匿名函数为 "replacer" 或 "替换函数",在替换函数的参数列表中,match、offset 和 string 在每一次匹配时总是存在的,而中间的 p1、p2 等捕获组,String#replace()方法会根据实际匹配情况去填充,当然,我们还可以根据 arguments 获取到这些参数值。
下面是代码运行后的控制台打印结果:
现在来看,指定一个函数要比指定一个字符串功能强的多,每次匹配都能获取到这些有用的信息,我们可以对其进行一些操作处理,最后再返回一个值,作为要替换的新子串。所以推荐在调用 String#replace() 方法时,使用上面这种方式。
上面是 String 类与正则相关的常用方法,需要注意的是,String#search() 和 String#match() 方法签名中参数均为正则对象,如果我们传递了其他类型的参数,会被隐式转换为正则对象,具体的步骤是先调用参数值的 toString() 方法得到字符串类型的值,然后调用 new RegExp(val) 得到正则对象:
- // -> String#search(new RegExp(val.toString()))
- '123 123'.search(1); // 0
- 'true false'.search(true); // 0
- '123 123'.search('\\s'); // 3
- var o = {
- toString: function() {
- return '\\s';
- }
- };
- '123 123'.search(o); // 3
- // -> String#match(new RegExp(val.toString()))
- '123 123'.match(1); // ["1"]
- 'true false'.match(true); // ["true"]
- '123 123'.match('\\s'); // [" "]
- var o = {
- toString: function() {
- return '1(23)';
- }
- };
- '123 123'.match(o); // "123", "23"]
而 split() 和 replace() 方法不会将字符串转为正则表达式对象,对于其他类型值,只会调用其 toString() 方法将参数值转为字符串,也不会进一步向正则转换,大家可以亲自测试一下。
以上就是正则的相关基本知识及常用方法,限于篇幅原因,更多关于正则表达式的内容,博主会安排在下一篇中介绍和讲解,敬请期待。
参考资料:
来源: http://www.cnblogs.com/liuhe688/p/6087795.html