浅谈JS中正则表达式(RegExp)的使用

关于正则表达式

正则表达式(即Regular Expression,在代码中常简写为regex、regexp或RE),是由一个字符序列形成的搜索模式,具体而言是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式。

说到模式,模式可分为简单跟复杂。简单的模式可以是一个单独的字符。更复杂的模式还包括更多的字符,并可用于解析、格式检查、替换等等。特别的,我们可以规定字符串中的检索位置以及要检索的字符类型等等。

语法
/ pattern / flags    // 以双斜杠"/"包裹的模式(pattern)和标识符(flags)组成
/ csdn /gi            //  举个例子

pattern

即正则表达式的主体,用以检索和匹配某些字符的文本表示。

flags

即模式的修饰符(modifier),用以辅助regex模式的执行功能,为可选项。其包括”g”、”i”和”m”,分别代表“全局”、“忽略大小写”和“多行”,可以同时使用。ES5后还加入了”u”和”y”,代表“unicode”和“sticky”。

这里不再具体罗列和阐述基础的语法,若想查看,请点这里

RegExp对象

概念

RegExp 对象表示正则表达式,它是对字符串执行模式匹配的强大工具。

除了上面所述的直接用双斜杠来表示的“直接量语法“”外,还有另一种,那就是创建 RegExp 对象:new RegExp(pattern, flags)

下面三种创建正则表达式的方法都是等效的:

/ab+c/i;
new RegExp('ab+c', 'i');
new RegExp(/ab+c/, 'i');

属性

常见的属性有三个,分别是 lastIndexflagssource 。其中,lastIndex 是最常用的也是最容易忽视的属性,它表示的是在全局模式下,上一次匹配后的索引值(注意非全局不带”g”的正则表达式对象不具备lastIndex属性!!);flags 上面已经提过,这里省略;source 指的是正则表达式的主体(模式)。

方法

RegExp对象拥有3种方法,即 .exec().test() 以及 .compile(),下面来谈谈它们的常见用法。

常见用法

RegExp.prototype.exec()

此方法必须包含类型为String的参数,执行后会返回一个值。该方法用于检索字符串中的正则表达式的匹配。

如果找到匹配,则将返回一个结果数组。否则,将返回 null。此数组的第 0 个元素是与正则表达式相匹配的文本,第 1 个元素是与 RegExp 的第 1 个子表达式相匹配的文本(如果有的话),第 2 个元素是与 RegExp 的第 2 个子表达式相匹配的文本(如果有的话),以此类推。

var txt="web2.0 .net2.0";
var regex1 = new RegExp('abc');       
var regex2=/(\w+)(\d)\.(\d)/g;         // 每个括号里的表达式代表每一种模式
regex1.exec('abcdbabdabc');         => ["abc"]
regex2.exec(txt);                           => ["web2.0", "web", "2", "0"]

上面的regex2的执行结果让你吃惊了吗?我们来谈谈括号里的玄学。

每个括号里的表达式代表着每一种模式,我们用美元符号$来标记。比如 /(\w+)(\d)\.(\d)/g 中的(\w+)可以用 $1 标识,那么剩下的自然也可以用 $2$3 来表示。

像上面的多模式匹配方法,常用来进行关键字的提取以及字符串的局部替换和删除等操作。后面将会陆续谈到这些用法。

除了数组元素和 length 属性之外,该方法还返回两个属性 — index & inputindex 属性储存匹配文本的第一个字符的位置,input 属性则存放被检索的字符串。后面我们将会发现,在非全局的 RegExp 对象调用该方法时,返回的数组与 .match()返回的数组是相同的。

在chrome上执行.exec()后看到的返回信息:

执行.exec()后看到的返回信息

注意,当 RegExp 的修饰符是 global 时,会在 lastIndex 属性指定的字符处开始检索字符串。当找到并匹配后,该方法将把 lastIndex 设置为匹配文本的最后一个字符的下一个位置。也就是说,我们可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0

填坑

如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把 lastIndex 属性重置为 0

var regex= new RegExp('abc','g');
regex.lastIndex;                                     // 0
regex.exec('abcadabc');                         // ["abc"], index : 0
regex.lastIndex;                                     // 3
regex.exec('abc');                                  // null
regex.lastIndex;                                    //  0

我们可以看到,匹配之前,regexlastIndex0;匹配成功之后,lastIndex 变为 3;匹配失败后,lastIndex 回到 0

其实不难得出,第一次匹配时,将从此次匹配文本的 lastIndex 处进行扫描,若匹配成功,则返回结果列表(数组),并把此次的 lastIndex 修改为上次匹配成功后的lastIndex(初始则为0)加上regex模式的长度之和,然后继续以此匹配;若无匹配,则返回nulllastIndex 则被重置为 0

所以,无论匹配文本如何变化,若继续使用同一正则表达式执行exec()方法时,其将一直以上次匹配后的 lastIndex 作为此次匹配文本的匹配起始点(index)进行扫描,以此进行匹配。

因此,我们不要忘记在必要的时候手动把 lastIndex 设置为 0

请注意,无论 RegExp 是否为全局模式,该方法都会把完整的细节添加到它返回的数组中。这就是 .exec().match() 的不同之处,后者在全局模式下返回的信息要少得多。因此我们可以这么说,在循环中反复地调用 .exec() 方法是唯一一种获得全局模式的完整模式匹配信息的方法。

RegExp.prototype.test()

该方法用于检测一个字符串是否匹配某个模式。如果字符串中有匹配的值返回 true ,否则返回 false

一个简单的例子:

var regex1=/abc/g;
regex1.test('abcdabc');     // "true"
填坑

先来看一段代码:

var regex1=/abc/g;
regex1.test('abcdabc');     // "true"
regex1.test('abcdabc');     // "true"
regex1.test('abcdabc');     // "false"   -- whhhhhhat?

最后一行代码的运行结果是不是很迷呢?

有些人不信这邪乎的事儿,立马换了不同的字符串试试:

var regex2=/abc/g;
regex1.test('abcdabc');           // "true"
regex1.test('cbabcdabc');       // "false"   --seriously?

可还是一样啊。但这并不是神马邪乎的事儿。

其实,这也是关于正则表达式的 lastIndex 的赋值机制的问题。理解透彻了就自然都懂了。

上面已经说过,只要把 lastIndex 设置为 0 就OK了,这里不再赘述。

再多说几句

有些人很马虎,在写文本(字符串)验证这一块儿的时候,往往忽略了lastIndex而造成了致命的“意外”。他们是这么写的,仅仅用了这么一句:if ( regex.test( someString ) ) 来判断是否通过检验。其实这样做真的是十分危险,十分致命的!我们在写那些验证代码的时候,必定会有大量的代码复用,正则便是其中一者。倘若忘记对regex的lastIndex进行重置,那么将会导致大量的验证都是无效的。

鉴于这种情况,最简单的便是在这块验证作用域的最后一行添加这句重置代码:regex.lastIndex=0。还有个比较好的方法,就是使用下面将提到的String.match()进行匹配验证,省去了每次都要记得添加的重置代码,直接用if ( someString.match( regex ) )进行判断即可。

String.prototype.split()

该方法用于把一个字符串分割成字符串数组。我们先来回顾一下它的语法

语法

someString.split(separator,howmany)

这里的 separator 既可以是字符串也可以是正则表达式,但为了追求更好的解决方法,我们一般使用正则表达式作为参数。

执行该方法后,若成功匹配separator,则返回一个划值数组,否则,返回null

举个例子,最简单的,比如:

var str="Jack plays football";
str.split(" ");                                 // ["Jack","plays","football"]

若进行 separator 为非统一字段的复杂操作时,正则表达式的作用就表现出来了:

var url="https://www.baidu.com"                                        // 以url提取为例
var regex=/\W/g
url.split(regex).filter(function(itm){return itm.length});;         //  ["https", "www", "baidu", "com"]

这样,我们就可以快速地知道一个站点的协议以及各个级别的域名了。是不是很酷呢?

String.prototype.match()

该方法可在字符串内检索指定的值或找到一个或多个正则表达式的匹配。类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。

语法

someString.match(StrOrRegex)        // 参数既可以是某个值也可以是正则表达式

执行该方法后,如找到对应的值,则会返回一个含值数组,否则返回null

"Hello world!".match(",");              // null
"Hello world!".match("!");              // ["!"]
"Hello world!".match(/\w+/g);       // ["Hello", "world"]
填坑

如果 regex 没有标志 g,那么 match() 方法在 string 中就只能执行一次匹配。

"Hello world!".match(/\w+/);        // ["Hello"]

特别的,如果对字符串进行多模式(有多个子表达式)匹配,成功后,将返回一个数组,其中存放了与它找到的匹配文本有关的信息。该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。除了这些常规的数组元素之外,返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在 string 中的位置,input 属性则声明的是对 string 的引用。

"Hello. world!".match(/(\w+)(\S)/);     //  ["Hello.", "Hello", "."]  index: 0, input: "Hello. world!"

如果 regex 具有标志符 “g“,则 match() 方法将执行全局检索,找到 string 中的所有匹配子字符串。需要注意的是,全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 string 中所有的匹配子串,没有 indexinput 属性。

"Hello. world!".match(/(\w+)(\S)/g);        // ["Hello.", "world!"]

还有一点需要留意,在全局检索模式下,该方法既不提供与子表达式匹配的文本的信息,也不声明每个匹配子串的位置。如果需要这些全局检索的信息,可以使用上述RegExp对象的exec()方法。

String.prototype.replace()

该方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

语法

stringObject.replace(RegexOrSubstr,replacement)

返回一个新的字符串,若找到匹配文本,则将其替换为 replacement 并返回,若无匹配,则返回原字符串。特别的,当 regex 不是全局的(不带”g“)时候,该方法只执行一次替换操作,而在全局模式下,将会把所有匹配文本进行替换。

"Hello. world!".replace( /\[/ , '' );           // "Hello. world"
"Hello. world!".replace( /\W/ , '' );         // "Hello world!"
"Hello. world!".replace( /\W/g ,'' );        // "Helloworld"
多模式(子表达式)替换

这个方法非常实用,用得好的话可以大大节省字符串的操作时间。

该方法将在字符串中查找与 regex 相匹配的子字符串,然后用 replacement 来替换这些子串。特殊的是,replacement 可以是字符串,也可以是函数。如果它是字符串,那么每个匹配都将由字符串替换。但是 replacement 中的 $ 字符具有特定的含义。如下表所示,从模式匹配得到的字符串将用于替换。

字符替换文本
$1、$2、…、$99与 regex 中的第 1 到第 99 个子表达式相匹配的文本
$&与 regex 相匹配的子串
$`位于匹配子串左侧的文本
$’位于匹配子串右侧的文本
$$直接量符号


注意:ECMAScript v3 规定,replace() 方法的参数 replacement 可以是函数而不仅仅是字符串。在这种情况下,每个匹配都调用该函数,它返回的字符串将作为替换文本使用。该函数的第一个参数是匹配模式的字符串。接下来的参数是与模式中的子表达式匹配的字符串,可以有 0 个或多个这样的参数。接下来的参数是一个整数,声明了匹配在 string 中出现的位置。最后一个参数是 string 本身。

比较常用的是字符串翻转:

"Billy, John".replace(/(\w+)\s*, \s*(\w+)/, "$2 $1");      // "John Billy"

此外,还常用来修饰匹配的字符串:

'"a", "b"'.replace(/"([^"]*)"/g, "($1)");		// "(a),(b)",注意$1被括号所包裹修饰

最后别忘了,replacement可以是一个回调函数,因此我们可以使用.replace()来进行文本格式化,比如:大小写转换。

"hello world".replace( /(\w+)/g , function(w){ return w.toUpperCase() } )       // "HELLO WORLD"

写在最后

由于这些方法都涉及到正则表达式,所以建议还是要多看几遍正则表达式的使用,彻底搞懂究竟是怎么一个回事。

篇幅有限,我仅在此谈了一些基础和使用方法,这些都是基于我有限的认识来写的,所以如果有哪些地方写得不对,还烦请各位读者的指正。同时也希望各位把能够把正则表达式发挥较好的方法指教于我,在此感激不尽!

预告一下,后期我将会再写一篇关于一些常用的表单验证写法,到时我们来讨论哪些方法比较好。

参考

W3school - RegExp & MDN - String.prototype

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值