JS 正则表达式详解 学习笔记

JS 正则表达式:(RegularExpression)

正则表达式的含义:

正则表达式是一种字符串匹配模式,在javascript中的作用主要是用来匹配特定形式的字符。

定义一个正则表达式:

我们想要定义一个正则表达式有两种方法:

  1. 正则表达式直接量
  2. 创建正则表达式对象

正则表达式直接量:

var reg = /s$/

我们可以使用正则表达式直接量来创建一个正则表达式,其中格式为/xxx/,用两个反斜杠来表示正则表达式创建的格式,反斜杠中间写上我们要匹配的字符文本。

创建正则表达式对象:

var reg2 = new RegExp("s$")

RegExp构造函数创建正则表达式其实也是很简单的,构造函数接收一个参数,参数类型为字符串类型。传入的字符串就是我们要匹配的字符串的模板。new RegExp()返回一个正则表达式对象。

注意:和js中其他直接量不同,正则表达式的直接量会在代码执行的时候会把/s$/转变成一个RegExp对象,在旧版本的es3中,当执行到同一段正则表达式的时候会返回相同的对象。举个例子:

function createRegExp(){
     
	var reg = /S$/
    return reg
}
var myreg1 = createRegExp()
var myreg2 = createRegExp()
console.log(myreg1 === myreg2)//true

我们定义了一个函数,用来创建一个正则表达式对象。在全局,我们执行了两次createRegExp()构造函数,将生成的正则表达式对象分别存放在myreg1myreg2中,当我们将其做相等运算时发现,它们指向的是相同的一个对象,也就是说,在es3中,用正则表达式常见的RegExp对象会共享同一个实例。其实这个结论的前提是正则表达式的模式要一致。但是在es5中则是两个相对独立的表达式对象:

console.log(myreg1 === myreg2)//false

myreg1和myreg2是相互独立的,并不都会相互共享内存信息。如:lastIndex后续有讲到。

直接量字符:

当我们想要去匹配非法的字符的时候,比如/的时候,我们需要对字符进行转移。通常的做法是在非法的字符前面加上\,即斜杠。

在正则表达式中,有一些字符有它自己独特的含义,这些字符有:^ $ . * + = ! : | \ / ( ) [ ] { }等,当我们使用这些符号的时候,它代表的意义已经不是我们字面上所看到的了,假如我们真的只是想单纯的想去匹配这些字符怎么办呢,我们需要对这些字符进行转义,假如我们想要匹配一个+

var reg = /+/

上面的写法肯定是不行的,原因是它代表特殊的匹配含义,想要单纯的匹配字符我们应该这样:

var reg = /\+/

我们需要用右斜杠将其转义后就可以单纯的匹配+字符了。

字符类:

将直接量字符单独放进方括号[]内可以组成一个字符类。一个字符类可以匹配它所包含的任意字符。我的理解是,字符类相当于一个集合,这个集合里面放着我们定义的字符。例如:

var reg = /[abc]/

注意:不用打逗号来表示分割,即不要[a,b,c]这样的话,也成为了我们字符类里的成员。

上面的表达式的意思是:现在我有一个集合,集合里面放的有ab,c这三个元素,那么正则表达式就用这个集合里的任意一个元素进行匹配。另外,我们可以用^来否定字符类,^符号在正则表达式的意思类似于我们编程语言中!的意思,即为非,[^abc]表示剔除掉集合中的所有元素,就是匹配集合的补集,除了集合中的元素,其他的元素都可以匹配。

[]表示的是字符类,但是有一点值得注意,一个[]代表一个字符,只不过这个字符可能是集合中的任意一个字符。什么意思呢,比如:

var str = '1234a'
var reg = /[asdfg]/
//现在我们的集合里面有a,s,d,f,g这五个元素,但是在匹配的时候只能和字符串中的一个进行匹配。
//先和str中的1匹配,看自己的集合中有没有这个字符,如果没有再和2匹配,看自己的集合里有没有这个字符
//以此类推,中括号里有五个字符,并不代表它一次能和字符串中的五个字符匹配。而是一个一个的匹配。

字符类可以使用连字符-,来表示字符的范围。如:[a-zA-Z0-9]:表示这个集合里面的元素是大小写字母和数字。每一个[]其实都有一个自己的集合。就算你这样写[(ab)(cd)]他也只是表示他的集合中有(/a/b/)/c/d这六个字符。

在js中,有一些特定的字符类:

  • [...]:方括号内的任意字符。也就是说这个集合里的元素是所有的字符。
  • [^...]:不在方括号内的任意字符。也就是说是[...]集合的补集,也就相当于这个集合为空,它匹配不了任何字符。
  • .:除了换行符和其他Unicode行终止符之外的任意字符。
  • \w:任何ASCII组成的单词,等价于:[a-zA-Z0-9]
  • \W:(大写),任何不是ASCII组成的单词。等价于:[^a-zA-Z0-9]
  • \s:任何Unicode空白符。
  • \S:任何非Unicode空白符的字符。注意:\w\S不一样。
  • \d:任何ASCII数字。等价于[0-9]
  • \D:除了ASCII数字之外的任何字符,等价于:[^0-9]
  • [\b]:退出直接量(特例)

在方括号内可以写这些特殊转义字符,比如:[\w\s]他代表的含义是匹配任何ASCII组成的单词和任何Unicode空白符。也就是说[\w\s]字符类的集合是\w\s的并集。

重复:

我们现在学的正则表达式只能匹配单个字符。意思是单个单个的字符匹配,无论是字符类还是使用特定的字符类。它们的区别只是对应集合的差异,在匹配的行为上,都是一次只能对一个字符进行匹配。假如我们要对多个字符进行匹配怎么办。有人会想到,我可以直接/abcd/,这样就可以一次性匹配四个字符了,也就是四个四个的去匹配。这样固然可以,但是有局限性。就是限定死了匹配的内容,只能匹配abcd这四个字。现在我们的需求是匹配两个任意的数字。那么我们应该怎么办呢?我们可以这样写:

var reg = /\d\d/
//这样就可以表示一次性匹配两个数字了

但是这样写有一个缺点,假如我们要同时匹配100个数字,难道我们要写100个\d吗。正则表达式对于重复有自己的相关优化。

下面是正则表达式的重复字符语法:

  • {n,m}:匹配前一项至少n次,但不能超过m次。
  • {n,}:匹配前一项n次或者更多次。
  • {n}:匹配前一项n次。
  • ?:匹配前一项0次或者1次,也就是说前一项是可选的,等价于{0,1}
  • +:匹配前一项1次或多次,等价于{1,}
  • *:匹配前一项0次或多次,等价于{0,}

上面的不是匹配的次数,而是一次要同时匹配几个字符。即以几个字符为单位去匹配。

我们来解决我们前面提到的需求,此时我们要匹配两个数字(两个两个的匹配,两个数字为一次匹配单位),则可以这样写:

var reg =/\d{2}/

实例:

这里有几个实例:

var reg1 = /\d{
     2,4}/ //匹配2~4个数字
var reg2 = /\w{
     3}\d?/ //精确匹配三个单词和一个可选数字
var reg3 = /\s+javascript\s+/ //匹配前后都带有一个或者多个空格的"javascript"字符串
var reg4 = /[^(]*/ //匹配0个或者多个的非(的字符

因为*?允许字符的匹配可以为0,所以它们允许什么都不匹配。

重复的特性:

贪婪匹配:

我们在分析前面正则表达式重复语法的时候发现,大多数的语法都允许我们去匹配多个。现在我们想一个问题,我有一个字符串,然后有一个reg

var str = "aaaa"
var reg = /a+/

此时问题就来了,因为+是允许我们去匹配一个或者多个字符。那么我们是匹配一个还是两个亦或者全部呢?答案是会匹配全部。在一般情况下,正则表达式使用的是贪婪匹配。即尽可能多的匹配。

非贪婪匹配:

假如我们不想进行贪婪匹配,我们可以使用非贪婪匹配,当我们使用非贪婪匹配的时候,只需要在待匹配的字符后面跟随一个?即可。例如:??,+?,*?,{1,3}?。比如正则表达式/\d{2,4}?/他也可以匹配两个或四个数字,但是它会尽可能少的匹配。这里挺有意思的,我们仔细思考什么叫尽可能少的匹配。也就是说在默写特定的情况下即使你使用了?也不能以最少的来匹配。这就引除了一条重要的概念:

正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置。

例如:

var str = "aaaab"
var reg = /a+?b/

我们也许希望它会匹配最后一个a和第一个b。但是真正的结果是它会匹配整个字符串。原因是因为正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置。

选择,分组和引用:

正则表达式的语法还包括指定选择项子表达式分组引用前一子表达式的特殊字符

指定选择项:在正则表达式中,我们可以使用|来分隔供选择的字符。例如:/ab|cd|ef/,意思时匹配abcdef。用集合理论的话就是现在的集合中有三个元素ab/cd/ef,看谁最先符合匹配的标准,一旦有一个元素符合匹配的机制就会停止匹配,无论它右面的元素是什么都会被忽略。

选择项的尝试匹配次序是从左到右的,直到发现了匹配项。如果左边的选择项被匹配的话,则会直接忽略右边的选择项。例如:

var str = "ab"
var reg = /a|ab/

因为a被首先的匹配到,所以ab会被自动忽略。

分组()和引用\n

在正则表达式中,圆括号有多种作用:

  1. 把单独的项组合成子表达式
  2. 在完整的模式中定义子模式
  3. 允许在同一正则表达式的后部分引用前面的子表达式

在正则表达式中,每一个()内的文本都是一个子表达式,有的子表达式可以引用有的却不能引用。后面有讲到。注意:在正则表达式中假如想要匹配’(’ 和 ")"需要进行转义。

把单独的项组合成子表达式

​ 把单独的项组合成子表达式,以便可以像处理独立单元那样来使用|,?,+等。

​ 项其实就是我们每一次进行模式匹配时所用到的字符模板。项的字符数可以是一个或者多个。比如说:

var str = 'ababab'
var reg = /ab|cd|bab/

这里的ab,cd,bab其实就是项,每一次去进行匹配的时候它会使用这三个里面的某一个去匹配。

var str = "abababab"
var str = /ababa/
//此时ababa就是一个项,他是一个整体,不会把它拆开来分别匹配。
var str = "abababab"
var reg = /abab{2}/
console.log(str.match(reg))
null
//我们可以看到,当使用{2}的时候,它前面的项是b。它等价于reg = /ababb/
//所以不同场景,项的字符个数不一样。

假如我们想要匹配上面字符串的abababab怎么办,当然,你可以直接写var reg = /abababab/但是这样写太麻烦了,我们可以这样写/(abab){2}/,此时,{2}会把(abab)看作一个整体,而不是单独的把最后一个b看作一个整体。其实还有更简单的方法/(ab){4}/它等价于/abababab/。实例:

var reg = /(ab|cd)+|ef/

上面的正则表达式表示,可以匹配ef,也可以匹配abcd的一次或多次。简单说,()在这里的作用就是为?/+/*/$/^/{}这些特殊语法服务的,就是想让它们知道,你要把哪些字符看做一个整体(项),然后一起操作。默认的情况下它们操作的项只有一个字符。

在完整的模式中定义子模式:

允许在同一正则表达式的后部分引用前面的子表达式

带圆括号表达式的另一个用途是允许在同一正则表达式的后面引用前面的子表达式。这是通过在字符\后面添加数字来实现的。形如:\3原本的数并不需要转义,但是我们却用了\字符,说明这个3有特殊含义。数字代表的是带有圆括号的子表达式在正则表达式的位置。例如:\1代表对正则表达式中的第一个子表达式进行引用,\2表示对正则表达式中的第二个子表达式进行引用,注意,因为子表达式可以嵌套另一个子表达式,所以它的位置是根据参与计数的左括号的位置来决定的。

注意:对正则表达式中前一个子表达式的引用,并不是指对子表达式模式的引用,而指的是与那个模式相匹配的文本的引用。**如何去理解这句话呢,我们看一段代码:

var reg = /(ab\d)ccc\1/
var str = "ab1cccab1"
console.log(str.match(reg))

我们也许很清楚的知道打印结果:

[ 'ab1cccab1', 'ab1', index: 0, input: 'ab1cccab1', groups: undefined ]

但是匹配的原理真的是你想像中的那样吗?我们再来看一个实例:

var reg = /(ab\d)ccc\1/
var str = "ab1cccab2"
console.log(str.match(reg))

结果:

null

为什么是这个样子,其实上面的那句话说的已经很明了了。对正则表达式前面子表达式的引用并不是对子表达式的模式的引用。什么是模式的引用?模式的引用指的是,比如现在有一个模式是/ab\d/表示匹配ab然后后面跟一个任意的数字,假如有一个正则表达式引用它的模式,则这个正则表达式也表示匹配ab然后后面跟一个任意的数字,但是人家说的很清楚了,不是引用模式而是引用与那个模式相匹配的文本,即\1代表的是(ab\d)匹配的文本,即:ab1,所以当我们设置str = "ab1cccab2"时,就无法得到匹配的原因。

这样,引用可以用于实施一条约束,即一个字符串的各个独立的部分都是相同的字符。比如:111111111111,假如我们要匹配这样的一个字符串的话,我们完全可以这样写:

var str = "aaaaaaaaaaaaa"
var reg = /(\w)(\1)+/
console.log(str.match(reg))

其中/(\w)(\1)+/表示,第一个是任意的字符,那么(\1)表示的是(\w)匹配到的字符,也就是a+表示a的数量可以有多个,那么就可以把整个字符串都匹配下来了。

还有一种子表达式:(?:…),例如:(?:java)。虽然这是一个表达式,但是它却不能引用,也就是说它并不在数字索引的考虑范围之内,例如:

var reg = /(java)(?:script)\2/
var str = "javascriptscript"
console.log(str.match(reg)) 

结果:

null

我们发现,并不能通过\2来获取script所以(?:)并不能参与正则表达式的数字索引。而它仅仅适用于分组

正则表达式的选择,分组和引用字符:

  • |:选择,匹配的是该符号左边的子表达式或者右边的子表达式
  • (...):组合,将几个项组合为一个单元,这个单元可通过*/+/?/|等符号加以修饰,而且可以记住这个组合项匹配的的字符串文本以供此后的引用使用。
  • (?:...):只组合,把项组合得到一个单元,这个单元可通过*/+/?/|等符号加以修饰,但不记忆与该组相匹配的字符。其实(?:...)(...)的区别就是一个匹配的文本能被引用而另一个却不能。
  • \n:n是数字类型,如:\1。和第n个分组第一次匹配的字符相匹配,组是圆括号中的子表达式(也有可能是嵌套的),组的索引是从左到右的左括号数,(?:形式的分组不编码。

指定匹配位置:

锚点:

在前面我们知道了关于匹配字符的正则表达式。但是,在正则表达式中,还有一种可以用来匹配位置的语法。

在正则表达式中这些匹配位置的语法元素通常我们叫它们锚元素。最常用的锚元素是^,它用来匹配字符串的开始。锚元素$用来匹配字符串的结束。当然还有\b表示匹配一个单词的边界。

零宽断言:

零宽断言是一种位置的匹配机制,它匹配到的内容不会保存到匹配结果中去,最终匹配结果只是一个位置而已。
作用是给指定位置添加一个限定条件,用来规定此位置之前或者之后的字符必须满足限定条件才能使正则中的字表达式匹配成功。

在js中只支持零宽先行断言。而零宽先行断言又可以分为正向零宽先行断言,和负向零宽先行断言

正向零宽先行断言(?=…):

var str = "ab123"
var reg = /ab(?=\d+)/
console.log(str.match(reg))
//[ 'ab', index: 0, input: 'ab123', groups: undefined ]

在这里,(?=\d+)表示的是正向零宽断言,意思是匹配的字符后面(是一个位置概念,类似于^和$)必须有一个或者多个数字才能匹配成功,我们发现,最后它返回的是ab,并没有把后面的数字给返回。因为零宽断言(?=\d+)并不匹配任何字符,只是用来规定当前位置的后面必须是一个或多个数字,也就是位置的限制条件。要把ab匹配成功,那么它的后面一定要有一个或多个的数字。我们发现,其实和我们熟知的锚点不同,锚点的位置匹配是固定的,没有任何的限制条件,但是零宽断言就不一样了,它必须有限制。

负向零宽先行断言(?!..):

var str = "ab123abc"
var reg = /ab(?!\d+)/
console.log(str.match(reg))
//[ 'ab', index: 5, input: 'ab123abc', groups: undefined ]

在这里,(?!...)表示的是负向先行断言,意思时匹配的字符后面必须没有一个或多个数字才能匹配成功。和正向零宽先行断言一样,负向的也只返回ab,因为零宽断言(?!\d+)并不匹配任何字符,只是用来规定当前位置的后面必须不能是一个或多个数字,也就是位置的限制条件。这里返回的index值为5,说明它匹配的ab并不是开头的那个,而是后面的。我们发现其实正向零宽断言是表示?=\d+,即这个位置必须有(等于)这个限制条件。而负向零宽断言是表示?!\d+,即这个位置必须不能有(不等于)这个条件。其实也就是字面意思!(非)不等于或者和正向的条件相反。

通过这两个实例,其实我们对零宽断言有一些的了解,我认为零宽断言弥补了正则中在字符串中间定位位置的机制,我们所熟知的锚点是在边界上定位位置。

修饰符:

正则表达式的修饰符用于更高级的模式匹配。修饰符的写法和我们平时的匹配模板不同,他并不是用在双斜杠内部的。而是外部,在第二个反斜杠的右侧,如:/\d/gg就是我们所说的修饰符。

正则表达式修饰符:

  • i:执行不区分大小写。
  • g:执行一个全局匹配,简而言之,即 找到所有的匹配,而不是在找到第一个后就停止
  • m:多行匹配,^匹配一行的开头和字符串的开头,$匹配行的结束和字符串的结束。

修饰符可以多个同时存在,比如:/javascript\d+/ig表示匹配是全局匹配且不区分大小写。

用于模式匹配的String方法:

对于正则匹配,我们一般有两个侧重点。一个是对字符串的侧重,另一个是对正则表达式的侧重。

其中在字符串中,我们有四种方法使用正则表达式的方法:

  1. search(reg):search方法有一个参数,代表接收的正则表达式。该方法返回的是第一个符合匹配的起始位置。如果匹配失败则返回-1search不支持全局检索也即是说它会忽略修饰符g。实例:

    console.log("1234".search(/\d/g))
    //0
    

    假如传入的参数不是正则表达式,则search会将其转换成正则表达式,例如:str.search("123")会转换成str.search(/123/)

  2. replace(reg,str):该方法的作用是执行检索和替换,它接收两个参数,第一个是要进行匹配的正则表达式,第二个参数是参与替换的字符串。该方法会对要匹配的字符串进行检索,将匹配成功的字符串替换成第二个参数中的字符串,返回一个替换好的新串,假如使用了模式匹配修饰符g,则不会只匹配一个,而是将整个字符串都检索,符合匹配的都被替换。如果传入的第一个参数是一个字符串,则会直接搜索这个字符串,然后进行替换,且只替换一次。因为它并没有收到全局匹配的指令**。如果在替换字符串中出现了$符号加数字,那么replace将用与指定的子表达式相匹配的文本来替换这两个字符。**replace方法的第二个参数可以是函数,

    该函数能够动态的机选替换字符串。

    插入特殊变量名:

    $$ 插入一个 “$”。
    $& 插入匹配的子串。
    $ 插入当前匹配的子串左边的内容。
    $ 插入当前匹配的子串右边的内容。
    $n 假如第一个参数是 RegExp对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始

    实例:

    var str = "asdfg12dfsdf34"
    var reg = /(\d)(\d)/g
    console.log(str.replace(reg,"$2"))
    //asdfg2dfsdf4
    //运行过程大致为,先匹配到了"12",然后用第二个子表达式的文本,也就是"2"来替换匹配到的"12"字符串。
    //然后继续向下匹配,匹配到"34",然后用第二个子表达式的文本,也就是"4"来替换匹配到的"34"字符串。
    //然后返回新串
    

    这里我们可以看到我们用$2匹配到的文本替换了reg匹配到的值。其实本质上,我们就是用第二个参数的值来替换匹配到的值。那么我们就可以灵活发挥了。例如:

    var str = "asdfg12dfsdf34"
    var reg = /(\d)(\d)/g
    console.log(str.replace(reg,"$2" + "AAAA"))
    //asdfg2AAAAdfsdf4AAAA
    //运行过程是这样的,我们先匹配到了"12",然后$2代表的是第二个子表达式匹配的文本,即"2",然后进行一个
    //字符串加法运算,然后把运算的结果当作参与替换的字符串,其实本质上,$2就是一个动态的实时再在的值,
    //之所以是动态的,原因在于每次匹配,第二个子表达式的 值会有所变化。
    

    replac方法中的第二个参数可以为一个函数,我们可以通过函数来对匹配的字符进行操作:

    var str = "asdfg12dfsdf34"
    var reg = /\d\d/g
    console.log(str.replace(reg,function(val){
           
        console.log(val)
        return "AA" + val
    }))
    

    结果:

    12
    34
    asdfgAA12dfsdfAA34
    
  3. match(reg):该方法是最常用的方法之一,该方法只有一个参数,该参数是我们要匹配的正则表达式。该方法的返回值是一个数组,若我们使用了修饰符g,则该方法会进行全局匹配,而返回的数组中就是全局匹配成功的字符。假如我们并不进行全局匹配,那么仍然返回一个数组,该数组的一个元素是我们匹配到的字符。而剩下的元素是子表达式匹配的文本。如:

    var str = "123"
    var reg2 = /\d/
    var reg1 = /\d/g
    var reg = /(\d)(\d)(\d)/
    console.log(str.match(reg))
    console.log(str.match(reg1))
    console.log(str.match(reg2))
    

    结果:

    [ '123', '1', '2', '3', index: 0, input: '123', groups: undefined ]
    [ '1', '2', '3' ]
    [ '1', index: 0, input: '123', groups: undefined ]
    
  4. split(string/reg):该方法用以将调用它的字符串拆分为一个子串组成的数组,使用的分割符就是传入的参数,例如

    console.log("1,2,3,4,5".split(","))
    //[ '1', '2', '3', '4', '5' ]
    console.log("1-2-3-4-5".split("-"))
    //['1','2','3','4','5']
    console.log("1 - 2".split(/\s-\s/))
    //['1','2']
    console.log("1 - 2 -        3".split(/\s*-\s*/))
    //[ '1', '2', '3' ]
    

    当我们传入正则表达式时,split会在字符串中匹配相应的字符,将匹配成功的字符作为分隔符来进行分割字符串。

RegExp对象:

我们在前面说过,当js引擎在对正则表达式的处理是:假如我们用直接量的方式去创建一个正则表达式,那么js引擎会把它转化成正则对象。想要获取正则表达式对象,可以通过RegExp构造函数:

var reg = new RegExp("abc")

这样我们就获得了一个匹配字符串abc的一个正则表达式对象。RegExp可以接收两个字符串参数。第一个参数是我们要匹配的字符串模板,也就是直接量正则表达式两个斜杠之间的文本。在这里需要注意,假如我们使用像\d\s\w\W等这种带有\字符的这种字符类,必须要把\进行转义,比如:

var reg = new RegExp("\\d")

在这里我们使用了\\d中的\进行了转义。RegExp构造函数的第二个参数是一个可选参数,它指定的是正则表达式的修饰符。不过只能传入i/g/m或者它们的组合。例如:

var reg = new RegExp("\\d","g")

既然是对象,那么就会有属性和方法,接下来我们来看一看正则表达式对象的属性和方法。

属性:

  1. source:只读,字符串类型。包含正则表达式的文本。
  2. global:只读,布尔类型。用以说明表达式是否含有修饰符g
  3. ignoreCase:只读,布尔类型。用以说明表达式是否含有修饰符i
  4. multiline:只读,布尔类型。用以说明表达式是否含有修饰符m
  5. lastIndex:可读写,number类型。如果匹配模式中使用了修饰符g,这个属性存储的是在整个字符串下一次检索开始的位置,这个属性在后面的对象方法中会用到。

方法:

  1. exec():该函数的第一个参数是待检索的字符串。可以传入第二参数,第二个参数是修饰符,实例:

    var str = "abcde"
    var reg = new RegExp("\\w\\w")
    console.log(reg.exec(str))
    //[ 'ab', index: 0, input: 'abcde', groups: undefined ]
    

    我们从代码中可以看到,exec方法对一个指定的字符串执行一个正则表达式,简言之,就是在一个字符中执行检索匹配。如果没有找到任何匹配那就返回null,但是如果找到了一个匹配,它将返回一个数组。然后不会再向后进行匹配。这个数组的第一个元素包含的是与正则表达式相匹配的字符串,余下的元素是与圆括号内的子表达式相匹配的子串,例如:

    var str = "abcde"
    var reg = new RegExp("(\\w\\w)(\\w\\w)")
    console.log(reg.exec(str))
    //[ 'abcd', 'ab', 'cd', index: 0, input: 'abcde', groups: undefined ]
    

    match方法不同,就算是你使用了修饰符g,单次执行该函数是不会去进行全局的匹配,实例:

    var str = "123"
    var reg = new RegExp("\\d","g")
    console.log(reg.exec(str))
    //[ '1', index: 0, input: '123', groups: undefined ]
    

    match方法单次执行会进行全局匹配:

    var str = "123"
    var reg = new RegExp("\\d","g")
    console.log(str.match(reg))
    //[ '1', '2', '3' ]
    

    虽然exec的功能简单,但是多次执行时有一点特别需要注意,那就是它是否带有修饰符g,我们来看一个例子:

    var str = "abcdefghijklmnopqrst"
    var reg = new RegExp("\\w\\w")
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    /*
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    */
    

    然后我们再来看一个例子:

    var str = "abcdefghijklmnopqrst"
    var reg = new RegExp("\\w\\w","g")
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    /*
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'cd', index: 2, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ef', index: 4, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'gh', index: 6, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'ij', index: 8, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'kl', index: 10, input: 'abcdefghijklmnopqrst', groups: undefined ]
    */
    

    我们观察上面的代码,我们可以发现,第一个实例是我们不加修饰符g,第二个实例我们加上了修饰符g,我们连续对同一个字符串调用exec我们可以从打印的结果上看。它们有天壤之别,在第一个实例当中,匹配的元素永远是ab,且index属性的值永远是0,而第二个实例中,匹配的是不同的字符,并且index属性的值一直是变化的。这就是修饰符gexec函数的宏观影响。当调用exec函数的正则表达式对象具有修饰符g时,它将把当前正则表达式对象的lastIndex属性设置为紧挨着匹配子串的字符位置。当同一正则表达式第二次调用exec函数时,他将从lastIndex所指示的字符处开始检索。如果exec没有检测到任何匹配结果,他会将lastIndex重置为0。这里有一个很有意思的实例:

    var str = "abcdefghijklmnopqrst"
    var str_2 = "zzxxccvvbb"
    var reg = new RegExp("\\w\\w","g")
    console.log(reg.exec(str))
    console.log(reg.exec(str))
    console.log(reg.exec(str_2))
    /*
    [ 'ab', index: 0, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'cd', index: 2, input: 'abcdefghijklmnopqrst', groups: undefined ]
    [ 'cc', index: 4, input: 'zzxxccvvbb', groups: undefined ]
    */
    

    我们可以从实例中对上面的描述会有一个更清楚的理解。当我们第一次执行reg.exec(str)后,此时的reg.lastIndex的值为2,当我们第二次执行reg.exec(str)时,因为需要从reg对象中的lastindex属性中获取我检索的位子,此时的reg.lastIndex的值为2,所以就从2位置开始检索,检索完毕后将reg.lastIndex的值设置为4并保存,当去匹配其他字符串的时候,需要从lastIndex获取开始匹配的位置,所以其他的字符串会从4这个位置开始匹配。

  2. test(str):该函数接收一个参数,就是我们所要匹配的字符串。该函数的作用是检测目标字符串中是否有我们要匹配的字符,如果有就返回true,如果没有就返回false。实例:

    var str = "1asdf"
    var reg = new RegExp("\\d")
    console.log(reg.test(str))
    //true
    

    当我们不添加修饰符g的时候,和exec方法一样,reg.lastIndex的值永远为0。每一次的匹配都要从最开始来匹配。但是当我们加上修饰符g的时候,事情就变得好玩了。我们来看一个实例:

    var str = "asd1f"
    var reg = new RegExp("\\d","g")
    console.log(reg.lastIndex)
    console.log(reg.test(str))
    console.log(reg.lastIndex)
    console.log(reg.test(str))
    console.log(reg.lastIndex)
    /*
    0
    true
    4
    false
    0
    */
    

    我们发现,当我们使用修饰符g的时候,和exec一样,当匹配成功时,lastIndex会被设置为紧挨着匹配子串的字符位置。当有其他的字符调用该正则表达式的test函数时,开始检索的位置就是reg.lastIndex保存的位置。当匹配失败,则会重置lastIndex的值为0

    exectest不同的是,字符串上的方法并不会用到lastIndex这个属性。本质上,String方法只是简单地将lastIndex的值重置为0。所以我们每次调用相关方法的时候都会从头开始检索。

感想:

上述的文章是我在学习js正则表达式自己归纳和理解的。仅供大家参考,若有错误,望指正。我将感激不尽,谢谢!

你可能感兴趣的:(前端,javascript,正则表达式)