理解javascript正则表达式

前端技术 2023/09/08 JavaScript

了解RegExp类型:

ECMAScript通过RegExp类型来支持正则表达式。 var expression=/pattern/flags; 

正则表达式的模式(pattern)部分:

可以是任何简单或复杂的正则表达式,可以包含字符类,限定符,分组,向前查找,反向引用。 关于正则表达式中各种特殊字符(如 \\,^,$,\\w,\\b 等)的含义可以参考 MDN 正则表达式-特殊字符 的整理。这里我们简单介绍一下向前查找和反向引用。

向前查找:正则表达式向前使用一些字符而不移动这些字符的位置,分为正向前预搜索也叫正向肯定查找( x(?=y) )与负向前预搜索也叫正向否定查找( x(?!y) )。
反向引用:标识字符串中可以提供的重复字符或字符串,可以使用捕获组反向引用匹配。带编号的反向引用 \\number number是正则表达式中捕获组的序号位置。
1、表达式 \\1~\\9 解释为反向引用而不是八进制代码。 /\\b(\\w+)\\s\\1/.exec(\'s_ s_\');//[\"s_ s_\", \"s_\"]
2、如果多位表达式的第一个数字是8或者9(如 \\80 或 \\91 ),则该表达式将被解释为文本。 /\\b(\\w+)\\s\\80/.exec(\'s_ 800\');//[\"s_ 80\", \"s_\"]
3、对于编号为 \\10 或更大值的表达式,如果存在与该编号对应的反向引用,则将该表达式视为反向引用。否则将这些表达式解释为八进制。

/(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)xx\\10/.exec(\'12345678910xx10\');//[\"12345678910xx10\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\"]
/(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)xx\\11/.exec(\'12345678910xx10\');//null

4、如果捕获组嵌套捕获组,捕获组确定的顺序是内部从外到内,外部从左到右。来个代码体会一下。
/\\b(\\w+x(x))\\s(\\1)/.exec(\'s_xx s_xxSTOP\');//[\"s_xx s_xx\", \"s_xx\", \"x\", \"s_xx\"]
5、如果正则表达式包含对未定义的组成员的反向引用,则会发生分析错误,根据语言的不同正则表达式引擎将引发 ArgumentEXception 。对于javascript会返回null。/\\b(\\w+)\\s\\2/.exec(\'s_ 8\');//null
反向引用实例代码:捕获组捕获到的内容不仅可以在正则表达式外部通过程序进行引用( RegExp.$n )也可以在正则表达式内部进行引用( \\number ,这种引用方式就是反向引用)。

//表达连续三个相同的小写,{2}应用在\\1身上
/([a-z])\\1{2}/.exec(\'aaa\');//[\"aaa\", \"a\"]
复制代码
//一道有意思的正则问题
/(\\w)((?=\\1\\1\\1)(\\1))/.exec(\'aa bbbb\');//[\"bb\", \"b\", \"b\", \"b\"]

/*这里捕获组有三个,$1为(\\w)中的内容,$2为((?=\\1\\1\\1)(\\1))中的内容:
需注意(?=\\1\\1\\1)并不是捕获组而是正则表达式的判断条件,x(?=y)表示匹配x仅仅当后面跟着y,判断条件并不是匹配结果的一部分。所以现在$2的内容为(\\1)即‘b\'。$3就是\\1的内容。返回的匹配项
“bb”中的第一个\'b\'为\"aa bbbb\"中的第一个\'b\',第二个\'b\'为\"aa bbbb\"中的第二个\'b\'。*/

/(\\w)(x(?=\\1\\1\\1)(\\1))/.exec(\'aa bxbbbcb\');//[\"bxb\", \"b\", \"xb\", \"b\"]
//这里$2的内容为(x(?=\\1\\1\\1)(\\1))中的内容即x(\\1);

//其实上面两种模式可以简化成/(\\w)(?=\\1\\1\\1)(\\1)/表示匹配\\w仅当该\\w后后面跟着三个\\1,然后获取的匹配项为该\\w且其后再紧跟着\\1的字符串。同理/(\\w)x(?=\\1\\1\\1)(\\1)/
复制代码
/(\\w)((?=\\1\\1\\1)(\\2))/.exec(\'aa bbbbv\');//[\"b\", \"b\", \"\", \"\"]
/*捕获组$2为((?=\\1\\1\\1)(\\2))中的内容,由于此时还未执行完捕获组$2处的匹配,所以\\2表示\"\"。$3即为\\2的内容还是\"\"。所以这条匹配被解释为返回\\w且其后紧跟3个该\\w的字符串,返回\\w+\'\'就只返回\'b\'了。*/

正则表达式的标志位(flags)部分:

可以带有一个或多个标志,用以表明正则表达式的行为。

1、g:表示全局模式,模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止。 \'cat mat bat\'.replace(/.(?=at)/g,\'A\');//\"Aat Aat Aat\"
2、i:不区分大小写模式,在确定匹配项时忽略模式与字符串的大小写。 \'cAt mat bAt\'.replace(/a/gi,\'B\');//\"cBt mBt bBt\"
3、m:多行模式,在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。

var str=\'cat\\nmat\\nbat\';
str.replace(/at/gm,\'AB\');
/*\"cAB
mAB
bAB\"*/

正则表达式中的元字符部分:

在模式中使用这些元字符时必须转义,如果想要匹配的字符串中包含这些字符,就需要对他们进行转义。

( [ { \\ ^ $ | ) ? * + . ] }
//匹配\"[bc]at\"
/\\[bc\\]at/.exec(\"xx[bc]at\");//[\"[bc]at\"]

//匹配\".at\"
/\\.at/.exec(\"xx.at\");//[\".at\"] 

创建正则表达式:

字面量形式:形如 var expression=/pattern/flags;
RegExp构造函数:两个参数(要匹配的字符串模式,可选的标志字符串),不能把正则表达式字面量传递给构造函数,虽然即使这样写了也不会报错。可以使用字面量定义的任何表达式都可以使用构造函数来定义。如下:

var p=/[bc]at/;
new RegExp(\'[bc]at\');// /[bc]at/

1、当不传任何参数或参数一为空字符串时, new RegExp();// /(?:)/  或 new RegExp(\'\');// /(?:)/ ,表示匹配 \"\" 但不记住匹配项( \"\" 其实就是 \":\" 之后的空串,不记住x匹配项的规则为(?:x)),所以在匹配任何字符串时都返回 [\"\"] 。所以由此可以猜想一下javascript正则引擎内部机制应该是默认匹配 \"\" 且不记住该匹配项,除非显式声明在 \":\" 之后的需要匹配的字符串,加上 \"(?:)\" 显式声明不记住匹配项。
2、由于构造函数模式参数是字符串,所以某些情况下(是指那些已经转义过的字符)对字符进行双重转义(即在字面量形式的单重转义再来一层转义)。某些情况下当然也可以进行单重转移( new RegExp(\'\\w\');// /w/ )。注意\'\\\'比较特殊,在字符串中也需要进行转义。

var p=/\\\\n/;//转义\\,字符\"\\\"在字符串中常需要被转义为\"\\\\\"
p.exec(\"\\\\nxx\");//[\"\\n\"]

var p=new RegExp(\"\\\\\\\\n\");// /\\\\n/ 如果想获得正则表达式字面量为/\\\\n/,需要在正则表达式中再来一层转义
p.exec(\'\\\\nxx\');//[\"\\n\"] 注意被匹配的字符串\'\\nxx\'中\\n的\\也被转义了

new RegExp(\'\\\\n\').exec(\"\\n\");// 
[\"
\"]
/*RegExp(\'\\\\n\')返回/\\n/,即意思匹配换行符*/

new RegExp(\'\\n\').exec(\"\\n\");//
[\"
\"]
/*new RegExp(\'\\n\')返回
/
/ ,表示并没有进行转义,而是返回字面量
/
/,意思匹配换行符
*/

3、下面给出一些单重,双重转义模式的参考:第几次转义在表中已标出,单代表第一次转义,双代表在已经有的转义的基础上再进行的转义。

RegExp的实例属性:

通过实例的属性可以获取有关模式的各种信息

 global :布尔值,表示是否设置了g标志。
 ignoreCase :布尔值,表示是否设置了i标志。
 multiline :布尔值,表示是否设置了m标志。
 lastIndex :整数,表示开始搜索下一个匹配项的字符位置,从0算起。前提是设置g标志时才会有用。
 source :正则表达式的字符串标志,按照字面量形式而非构造函数中的字符串模式返回字符串。

new RegExp(\'\\\\\\\\w\');// /\\\\w/ 返回自面量形式正则表达式
new RegExp(\'\\\\\\\\w\').source;// \"\\\\w\" 字符串

RegExp的实例方法:

exec():该方法是专门为捕获组而设计的,参数为要匹配的字符串,返回包含第一个匹配项信息和可能有的捕获组的数组,若未匹配到返回 null 。(返回的虽然是 Array 的实例,但还包含两个额外的属性: index 表示匹配项在字符串中的位置, input 表示应用正则表达式的字符串)

var arr=new RegExp(\'\\\\\\\\(w)\').exec(\'\\\\w\');// [\"\\w\", \"w\"]
arr;// [\"\\w\", \"w\"]
arr.index;//0
arr.input;// \"\\w\" 即exec()里的内容

exec() 和 match() 方法的区别:

1、对于 exec() 而言,即使在模式中设置了全局标志g,它每次也只返回一个匹配项;字符串的 match() 方法在设置g的时候可以返回全部匹配项而没有捕获组且返回的数组没有index和input属性。

2、对于exec()而言可以返回捕获组,但match()在没有全局g标志时才能返回捕获组,此时match()返回的数组有index和input属性。

//返回全局匹配项演示比较
var arr=\'ababcdab\'.match(/ab/g);// [\"ab\", \"ab\", \"ab\"]
arr.index; // undefined
arr.input; // undefined
/ab/g.exec(\'ababcdab\');// [\"ab\"]

//捕获组演示比较,match()方法和有无设置全局g标志有关
\'ababcdab\'.match(/a(b)/g);// [\"ab\", \"ab\", \"ab\"]
var arr=\'ababcdab\'.match(/a(b)/);// [\"ab\", \"b\"]
arr.index;// 0
arr.input;// \'ababcdab\'
/a(b)/g.exec(\'ababcdab\');// [\"ab\", \"b\"] 

3、所以在选择使用方法的时候要先考虑好侧重该方法的哪方面功能,在不设置全局标志g的情况下,在同一个字符串上多次调用exec()则总是返回第一个匹配项的信息,而在设置全局标志的情况下,每次调用exec()则都会在字符串中沿着上次查找的位置往后继续查找新的匹配项。

//未设全局,每次从头开始查找
var p=/a/;
var str=\'ababa\';
var a=p.exec(str);// [\"a\"];
var b=p.exec(str);// [\"a\"];
a==b;// false
a.index==b.index;// true

//设置全局,沿着上次位置继续查找新匹配
var p=/a/g;
var str=\'ababa\';
var a=p.exec(str);// [\"a\"]
a.index;// 0
var b=p.exec(str);// [\"a\"]
b.index;// 2

test():接收字符串参数,在模式与字符串参数匹配情况下返回 true ,否则返回 false 。常被用在 if() 中当判断条件。

var text=\"000-000-000\";
var p=/((\\d{3})-)\\1*\\2/; if(p.test(text)){
 console.log(\'匹配成功\');
}

RegExp 实例继承 Object 的 toLocaleString() 和 toString() 方法都会返回正则表达式的字面量形式的字符串,与如何创建正则表达式的方式无关。 valueOf() 则返回正则表达式字面量本身。

var p=/\\[new\\]bi/;
p.toLocaleString();// \"/\\[new\\]bi/\"
p.toString();// \"/\\[new\\]bi/\"
p.valueOf();// /\\[new\\]bi/

var p=new RegExp(\'\\\\[new\\\\]bi\');
p.toLocaleString();// \"/\\[new\\]bi/\"
p.toString();// \"/\\[new\\]bi/\"
p.valueOf();// /\\[new\\]bi/

RegExp的构造函数属性:

构造函数本身包含一些属性(静态属性),这些属性适用于作用域中的所有表达式,并且基于所执行的最近一次正则表达式操作而变化。有长属性名(如下代码)和短属性名(即$前缀形式,由于这些符号大多不是有效的ECMAScript标识符,所以不能直接在 RegExp 构造函数上以 \".\" 的方式访问,而要通过方括号语法来访问)两种方式访问这些属性

/(.)hort/g.exec(\'this is a short day\');// [\"short\", \"s\"]

//最近一次要匹配的字符串
RegExp.input;// \"this is a short day\" 或RegExp[\"$_\"]访问;

//最近一次的匹配项
RegExp.lastMatch;// \"short\" 或RegExp[\"$&\"]访问;

//在最近一次要匹配的字符串中的最近一次匹配项之前的文本 
RegExp.leftContext;// \"this is a \" 或RegExp[\"$`\"]访问;

//在最近一次要匹配的字符串中的最近一次匹配项之后的文本 
RegExp.rightContext;// \" day\" 或RegExp[\"$\'\"]访问;

//最近一次(最后一次)匹配的捕获组
RegExp.lastParen;// \"s\" 或RegExp[\"$+\"]访问;

捕获组访问属性:还有9个用于存储捕获组的构造函数属性,访问语法是 RegExp.$n ,其中n取值1~9,用于获取第n个匹配的捕获组。在调用 exec() , test() 或 match() 等正则系列方法时这些属性会被自动填充。

var text=\"this is a short summer\";
var pattern =/(..)or(.)/g;
if(pattern.test(text)){
 console.log(RegExp.$1); // sh
 console.log(RegExp.$2); // t 
}

模式的局限性:

缺少一些高级正则表达式的特性,如不支持向后查找,命名的捕获组(形如 \\k<name> 引用之前名为 name 的捕获组的字符串)等。

推荐专题 《javascript正则表达式使用说明》

以上就是本文的全部内容,希望对大家的学习有所帮助。

本文地址:https://www.stayed.cn/item/23266

转载请注明出处。

本站部分内容来源于网络,如侵犯到您的权益,请 联系我

我的博客

人生若只如初见,何事秋风悲画扇。