JavaScript一些怪异的正则表达式行为分析

1.^ $ 及转义

regex= new RegExp("(^|\\s)" + "[a-z]\+\?" + "(\\s|$)","g")

分析:对于RegExp对象,转义都要加斜杠\,对于预定义的元字符就是双斜杠了,所以如果换成字面量表达式,结果如下式:

regex = /(^|\s)[a-z]+?(\s|$)/g;

(^|\s)表示字符在首位或者以空格开头,类似(\s|$)表示字符在结尾或者以空格结尾。所以上式相当于一下4个表达式的或关系:

/^[a-z]+?\s/g  /\s[a-z]+?\s/g  /\s[a-z]+?$/g  /\s[a-z]+?$/g 

+?问好表示非贪婪模式,/g表示找到第一个匹配时不会停止,会继续查找。所以以下语句的结果为:“返回值: d , ee”

    var str=" d d  ee ";//注:d和d之间只有1个空格,d和ee之间有2个空格    
    regex = /(^|\s)[a-z]+?(\s|$)/g;
    var result=str.match(regex);
    document.write("返回值: " +  result);

2.\b及\B

\b匹配一个单词边界,即字与空格间的位置,\B匹配非单词边界匹配

    var str=" d d  ee ";//注:d和d之间只有1个空格,d和ee之间有2个空格  
    regex = /(^|\s)[a-z]+?(\s|$)/g;
    regexx=/\b[a-z]+?\b/g;
    var result1=str.match(regex);
    var result2=str.match(regexx);
    document.writeln("返回值: " +  result1);// d , ee 
    document.writeln("返回值: " +  result2);//d,d,ee

输出为:
返回值: d , ee
返回值: d,d,ee
\b匹配一个单词边界也就是说字母数字或者下划线的边界位置,即\w的边界,反过来\B就是表示\w内部位置。比如:/\Bapt/表达式匹配 Chapter 中的字符串 apt,但不匹配 aptitude 中的字符串 apt。

结合以上两点,汇总来说:^表示字符串开始位置,$表示字符串结束位置,\b表示字符串内单词边界,\B表示字符串内位置

3./g及match、exec的组合效果

我们知道/g表示执行全局匹配,即在查找到第一个匹配的时候不会停止,会继续查找所有的匹配。match表示找到整体及分组表达式的匹配,而当正则表达式用括号进行分组时,match对有g和无g的结果截然不同。
n=“abcabc”.match(/(b)( c)/)的结果为n=[“bc”, “b”, “c”],为一个数组,数组的元素分别为第一次整体匹配的结果及本次结果各个分组匹配的结果。 n=“abcabc”.match(/(b)( c)/g)的结果为n=[“bc”, “bc”],没有单个的"b"及"c",也就是说有了g之后,执行的是全局的整体匹配,match忽视了分组匹配结果。或者说有了g之后,往往整体匹配结果就是一个数组,如果再考虑分组匹配的结果就混乱了。

与match不同的是exec是正则表达式对象的方法:
let myexec=RegExpObject.exec(string);
当正则表达式没有/g,也就是说非全局正则表达式的时候,其类似于match:如果 exec() 找到了匹配的文本,则返回一个结果数组。否则,返回 null。此数组的第 0 个元素是与正则表达式相匹配的文本,第 1 个元素是与 RegExpObject 的第 1 个子表达式相匹配的文本(如果有的话),第 2 个元素是与 RegExpObject 的第 2 个子表达式相匹配的文本(如果有的话),以此类推。除了数组元素和 length 属性之外,exec() 方法还返回两个属性。index 属性声明的是匹配文本的第一个字符的位置。input 属性则存放的是被检索的字符串 string。
当RegExpObject 是一个全局正则表达式时,exec() 的行为就稍微复杂一些。它会在RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符在字符串的位置编号,其编号从1开始计数。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。同时每次匹配的结果都是一个数组,包括完整的匹配及分组的匹配,见例子1。

例子1:

    var str = "Visit2 W3Schoolf  W3Schoolfsd ";
    var patt = /.(w3s)(ch)\w+/ig;
    var result;
    while ((result = patt.exec(str)) != null)  {
        document.write(result);
        document.write("<br />");
        document.write(result.length);
        document.write("<br />");
    }
    /*-------------------------------
    输出为:W3Schoolf,W3S,ch
           3
           W3Schoolfsd,W3S,ch
           3*/

由上例可知/g模式下,exec既有match没有/g模式下特点,又有自己独特的特性。

例子2:

    var str = "Visit W3Schiool";
    var patt1 = /i[\w]+/g;
    var patt2 = /(i[\w]+)/;
    var result1 = patt1.exec(str);
    var result2 = patt2.exec(str);
    var result3 = patt1.exec(str);
    patt1.lastIndex=0;
    var result4 = patt1.exec(str);
   
    document.writeln(result1);//isit
    document.writeln("<br />");
    document.writeln(result2);//isit,isit
    document.writeln("<br />");
    document.writeln(result3);//iool
    document.writeln("<br />");
    document.writeln(result4);//isit
    document.writeln("<br />");

4.正则表达式反向引用及多层分组模式序列界定以及匹配结果引用$1等

对一个正则表达式模式或部分分组模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 \n 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

var str = "Is is the cost of of gasoline going up up";
var patt1 = /\b([a-z]+) \1\b/ig;
document.write(str.match(patt1));//输出为:Is is,of of,up up

\1表示对第一个括号内匹配的内容进行复制引用,内容一定要一致,而+号表示通一类型,/[a-z]+/表示数量大于等于1个字母且是贪婪模式,但是不要求字母一致,而/t+/表示数量大于等于1个t且是贪婪模式。
可以使用非捕获元字符 ?:来重写捕获,忽略对相关匹配的保存。

 var str="cbcbb";
    regex =/(?:c)([cbz])\1/;
    var result1=str.match(regex);
    document.writeln(result1);//输出为:cbb,b

以上第一个括号内有?:,就表示此部分为非存储或者说非捕获的缓冲区,第二个括号内没有?:所以是存储的缓冲区,所以这个缓冲区的编号为1,那么\1就表示对这一缓冲区的引用。
对于层层嵌套的括号分组,按照括号先后顺序来界定顺序,如下列,根据非捕获转义符?:在哪个括号内来决定哪个括号分组为非捕获分组:

  const pattern = /((ninja-)+)sword/;
  const ninjas = "ninja-ninja-sword".match(pattern);
  document.writeln(ninjas)//[ninja-ninja-sword,ninja-ninja-,ninja-]

RegExp.$1是RegExp的一个属性,指的是与正则表达式匹bai配的第一个 子匹du配(以括号为标志)字符串,以此类推,RegExp.$2,RegExp.$3,…RegExp.$99总共可以有99个匹配

var r= /^(\d{4})-(\d{1,2})-(\d{1,2})$/; //正则表达式 匹配出生日期(简单匹配)
r.exec('1985-10-15');
s1=RegExp.$1;
s2=RegExp.$2;
s3=RegExp.$3;
alert(s1+" "+s2+" "+s3)//结果为1985 10 15

另外$经常和replace方法配合使用:

"fontFamily".replace(/([A-Z])/g,"-$1").toLowerCase//结果为:font-family

5.零宽断言 ?=、?<=、?!、?<!=

通过前面的第1和第2条,我们知道javascript有^ $ \b \B 4个固定的位置匹配符,那如何建立自定义的位置匹配呢?这就要通过零宽断言表达式来实现,其分为先行断言和后行断言:

正则表达式的先行断言和后行断言一共有 4 种形式:>
(?=pattern) 零宽正向先行断言(zero-width positive lookahead assertion)
(?!pattern) 零宽负向先行断言(zero-width negative lookahead assertion)
(?<=pattern) 零宽正向后行断言(zero-width positive lookbehind assertion)
(?<!pattern) 零宽负向后行断言(zero-width negative lookbehind assertion)

(?=pattern)代表字符串中的一个位置,紧接该位置之后的字符序列能够匹配 pattern:

    var str="a reggular expression";
    regex =/re(?=g)(g)\1/;
    var result1=str.match(regex);
    document.writeln(result1);//输出为:regg,g

/re(?=g)(g)\1/表示后面是g的re,然后紧跟g,最后的反向引用指向(g)缓冲区,这说明零宽断言内的缓冲区也是不存储的。
(?!pattern)类似,只不过表示字符串中的一个位置,紧接该位置之后的字符序列不能匹配 pattern,其也是缓冲区不存储的。
(?<=pattern)代表字符串中的一个位置,紧接该位置之前的字符序列能够匹配 pattern:其也是缓冲区不存储的。

    var str="regex represents regular expression";
    regex =/(?<=p)re(s)\1/;
    var result1=str.match(regex);
    document.writeln(result1);//输出为:ress,s

(?<!=pattern)代表字符串中的一个位置,紧接该位置之前的字符序列不能够匹配 pattern:其也是缓冲区不存储的。

6.组合".?" “.+?” "." “.+”

我们知道.表示匹配任意出换行符外的字符,表示匹配的字符可以有任意次,+表示匹配的字符至少1次,在或+后表示非贪婪模式

var str = 'Anna is {age} years old,Bob is {} years old too';
var expr1 = /{.+?}/g;
var expr2 = /{.*?}/g;
var expr3 = /{.*}/g;
console.log(str.replace(expr1, '13'));//Anna is 13 years old,Bob is {} years old too
console.log(str.replace(expr2, '13'));//Anna is 13 years old,Bob is 13 years old too
console.log(str.replace(expr3, '13'));//Anna is 13 years old too

由例子可知“.?”表示从满足“.”前的匹配开始到满足“?“后第一个匹配的位置结束这一段匹配。“.+?”类似,只不过非换行字符至少要出现一次。而"."是从满足“.”前的匹配开始到最后一个匹配的位置结束这一段匹配。

7.匹配所有字符

/[\s\S]/及/(.|\s)/可以用来匹配JavaScript中的所有字符。

8.replace函数的一些特殊应用

前面已经说过$和数字的组合可以是replace的参数,表示对匹配分组的引用:

let str="good luck";
let b1=str.replace(/(good)/,"Bob$1");
let b2=str.replace(/good/,"Bob$1");
let b3=str.replace(/(good)/,"Bob$2");
document.writeln(b1);//Bobgood luck
document.writeln(b2);//Bob$1 luck
document.writeln(b3);//Bob$2 luck

上例可知,如果有匹配分组,那么就引用,如果没有匹配分组,就直接按照字符来处理.

$&(美元符号+连字符): 与正则表达式相匹配的子字符串
$`(美元符号+切换技能键): 位于匹配子字符串左侧的文本
$’(美元符号+单引号): 位于匹配子字符串右侧的文本
$$: 表示 $ 符号

let str="good luck";
let b1=str.replace(/good/,"Bob $&");
let b2=str.replace(/good/,"Bob $`");
let b3=str.replace(/good/,"Bob $'");
document.writeln(b1);//Bob good luck
document.writeln(b2);//Bob  luck
document.writeln(b3);//Bob  luck luck

replace() 方法的第二个参数也可以是函数,在这种情况下,如果replace() 方法的第一个参数如果是字符串,那么匹配替换只执行一次,为函数传递的参数和执行字符串(非正则表达式)的exec方法返回的结果一样,分别是匹配结果,index和input属性。
如果replace() 方法的第一个参数如果是正则表达式且是全局模式,每个匹配都调用一次该函数,它返回的字符串将作为替换文本使用,这种情况下,即使函数没写明形参,参数也会传给函数的arguments数组的。arguments[0]获得匹配本次匹配结果,arguments[i]获得每个分组匹配的结果,倒数第二个arguments元素获得匹配在字符串对象中的位置,最后一个arguments元素获得字符串对象本身,也就是说,最后两个参数分别相当于exec方法执行结果的的index和input属性。如果函数有形参,那么就按照顺序把arguments数组元素赋值给各个形参。这种情况也类似于exec方法执行全局匹配模式的正则表达式的返回结果。

        var url = "https://www.jb51.net/o.php?mod=viewthread&tid=14743&extra=page%3D1";
        console.group("正则表达式");
        var regexp_global = /[?&](\w+)=([^&]*)/g;
        var count = 0;
        var twoResult = url.replace(regexp_global,function(){
            console.log("第"+(count++)+"次运行");
            console.log("replace输入参数:%o",arguments);
            var val = regexp_global.exec(url);
            console.log("exec输出参数:%o",val);

            console.assert(arguments[0] === val[0]);
            console.assert(arguments[1] === val[1]);
            console.assert(arguments[2] === val[2]);
            console.assert(arguments[3] === val["index"]);
            console.assert(arguments[4] === val["input"]);
            return count;
        });
        console.log("replace返回字符串:"+twoResult);
        console.groupEnd("正则表达式");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值