在JavaScript中如何连接正则表达式字面量?

189

能否像这样做?

var pattern = /some regex segment/ + /* comment here */
    /another segment/;

我必须使用新的RegExp()语法并连接一个字符串吗?我更喜欢使用字面量,因为代码更加自明且简洁。

或者我需要使用新的RegExp()语法并拼接一个字符串吗?我更倾向于使用字面量,因为代码更加易懂并且简洁。


3
如果您使用String.raw(),处理转义的正则表达式字符会更容易:let regexSegment1 = String.raw\\shello\s`` - iono
13个回答

223
以下是如何创建一个正则表达式而不使用正则表达式字面量语法的方法。这样可以在将其变成正则表达式对象之前进行任意字符串操作:
var segment_part = "some bit of the regexp";
var pattern = new RegExp("some regex segment" + /*comment here */
              segment_part + /* that was defined just now */
              "another segment");

如果你有两个正则表达式字面量,你实际上可以使用这种技术将它们连接起来:

var regex1 = /foo/g;
var regex2 = /bar/y;
var flags = (regex1.flags + regex2.flags).split("").sort().join("").replace(/(.)(?=.*\1)/g, "");
var regex3 = new RegExp(regex1.source + regex2.source, flags);
// regex3 is now /foobar/gy

这只是比起将表达式一和表达式二作为字面字符串而不是字面正则表达式来说更啰嗦。

2
请记住,使用这种方法时,每个段落必须是一个有效的正则表达式。构建类似于 new RegExp(/(/.source + /.*/.source + /)?/.source); 的表达式似乎不起作用。 - Sam
这个解决方案在反向匹配组的情况下不起作用。请查看我的答案,以获取在该情况下的可行解决方案。 - Mikaël Mayer
如果您需要转义一个字符,请使用双反斜杠:new Regexp('\$' + "flum") - Jeff Lowery
如果需要,您可以通过"<regexp>.flags"访问标志,因此理论上您也可以将它们组合在一起。 - bnunamak
2
你从哪里得到了 expression_one?你是不是指的是 regex1 - TallOrderDev

45

随意连接正则表达式对象可能会产生一些负面影响。请改用RegExp.source

var r1 = /abc/g;
var r2 = /def/;
var r3 = new RegExp(r1.source + r2.source, 
                   (r1.global ? 'g' : '') 
                   + (r1.ignoreCase ? 'i' : '') + 
                   (r1.multiline ? 'm' : ''));
console.log(r3);
var m = 'test that abcdef and abcdef has a match?'.match(r3);
console.log(m);
// m should contain 2 matches

这还可以使您能够使用标准RegExp标志保留先前RegExp的正则表达式标志。

jsFiddle


1
可以使用 RegExp.prototype.flags 进行改进。 - Parzh from Ukraine
为什么只检查r1的标志? - aderchox

23

我不完全同意“eval”选项。

var xxx = /abcd/;
var yyy = /efgh/;
var zzz = new RegExp(eval(xxx)+eval(yyy));

使用该源代码会得到“//abcd//efgh//”,这不是期望的结果。

var zzz = new RegExp(xxx.source+yyy.source);

将会得到"/ abcdefgh /"这是正确的。

从逻辑上讲,没有必要评估,你知道你的表达式。你只需要知道它的源代码或者它的写法,而不一定需要知道它的值。至于标志,你只需要使用RegExp的可选参数。

在我的情况下,我遇到了一个问题,就是^和$被用于几个我正在尝试连接在一起的表达式中!这些表达式是程序中使用的语法过滤器。现在我想把它们中的一些组合起来,以处理介词的情况。 我可能需要"切割"源代码,以去掉开头和结尾的^(和/或)$ :) 祝好, Alex.


我喜欢使用source属性。如果你和我一样使用jslint,那么如果你做类似这样的事情:var regex = "\.\..*",它会发出警告。 - Nils-o-mat
我不太同意eval选项。” - 你从哪里得到的这个信息? - Bergi

9

问题 如果正则表达式中包含像 \1 这样的反向引用分组。

var r = /(a|b)\1/  // Matches aa, bb but nothing else.
var p = /(c|d)\1/   // Matches cc, dd but nothing else.

然后仅仅连接这两个资源是不起作用的。实际上,两者的组合是:
var rp = /(a|b)\1(c|d)\1/
rp.test("aadd") // Returns false
解决方案: 首先,我们计算第一个正则表达式中匹配组的数量。然后对于第二个正则表达式中的每个回溯匹配标记,我们将其增加与匹配组数量相同的值。
function concatenate(r1, r2) {
  var count = function(r, str) {
    return str.match(r).length;
  }
  var numberGroups = /([^\\]|^)(?=\((?!\?:))/g; // Home-made regexp to count groups.
  var offset = count(numberGroups, r1.source);    
  var escapedMatch = /[\\](?:(\d+)|.)/g;        // Home-made regexp for escaped literals, greedy on numbers.
  var r2newSource = r2.source.replace(escapedMatch, function(match, number) { return number?"\\"+(number-0+offset):match; });
  return new RegExp(r1.source+r2newSource,
      (r1.global ? 'g' : '') 
      + (r1.ignoreCase ? 'i' : '')
      + (r1.multiline ? 'm' : ''));
}

测试:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true

2
这个函数是可关联的,所以你可以使用以下代码:function concatenateList() { var res = arguments[0]; for(var i = 1; i < arguments.length; i++) { res = concatenate(res, arguments[i]); } return res; } - Mikaël Mayer

6
提供以下条件:
  • 您知道在正则表达式中要做什么;
  • 您有许多正则表达式片段来形成一个模式,并且它们将使用相同的标志;
  • 您认为将小的模式块分开放在数组中更易读;
  • 您希望能够为下一个开发人员或自己稍后注释每个部分;
  • 您更喜欢将正则表达式视觉上简化,例如/this/g而不是new RegExp('this', 'g')
  • 您可以在额外步骤中组装正则表达式,而不是从一开始就拥有它。
那么您可能喜欢以这种方式编写:
var regexParts =
    [
        /\b(\d+|null)\b/,// Some comments.
        /\b(true|false)\b/,
        /\b(new|getElementsBy(?:Tag|Class|)Name|arguments|getElementById|if|else|do|null|return|case|default|function|typeof|undefined|instanceof|this|document|window|while|for|switch|in|break|continue|length|var|(?:clear|set)(?:Timeout|Interval))(?=\W)/,
        /(\$|jQuery)/,
        /many more patterns/
    ],
    regexString  = regexParts.map(function(x){return x.source}).join('|'),
    regexPattern = new RegExp(regexString, 'g');

你可以像这样做:

string.replace(regexPattern, function()
{
    var m = arguments,
        Class = '';

    switch(true)
    {
        // Numbers and 'null'.
        case (Boolean)(m[1]):
            m = m[1];
            Class = 'number';
            break;

        // True or False.
        case (Boolean)(m[2]):
            m = m[2];
            Class = 'bool';
            break;

        // True or False.
        case (Boolean)(m[3]):
            m = m[3];
            Class = 'keyword';
            break;

        // $ or 'jQuery'.
        case (Boolean)(m[4]):
            m = m[4];
            Class = 'dollar';
            break;

        // More cases...
    }

    return '<span class="' + Class + '">' + m + '</span>';
})

在我的特定情况下(类似于CodeMirror的编辑器),执行一个大的正则表达式要比进行许多替换更容易,例如每次用HTML标记替换表达式时,下一个模式会更难以针对而不影响HTML标记本身,并且没有好的后置断言,这在javascript中不幸不被支持。
.replace(/(\b\d+|null\b)/g, '<span class="number">$1</span>')
.replace(/(\btrue|false\b)/g, '<span class="bool">$1</span>')
.replace(/\b(new|getElementsBy(?:Tag|Class|)Name|arguments|getElementById|if|else|do|null|return|case|default|function|typeof|undefined|instanceof|this|document|window|while|for|switch|in|break|continue|var|(?:clear|set)(?:Timeout|Interval))(?=\W)/g, '<span class="keyword">$1</span>')
.replace(/\$/g, '<span class="dollar">$</span>')
.replace(/([\[\](){}.:;,+\-?=])/g, '<span class="ponctuation">$1</span>')

5

您可以从文字直接量和RegExp类中拼接正则表达式源代码:

var xxx = new RegExp(/abcd/);
var zzz = new RegExp(xxx.source + /efgh/.source);

4

尽可能使用字面量语法更好。它更短,更易读,而且您不需要转义引号或双重转义反斜杠。来自“Javascript Patterns”,Stoyan Stefanov 2010。

但是使用New可能是连接的唯一方法。

我会避免使用eval。它不安全。


1
我认为复杂的正则表达式如果像问题中那样分解并加上注释,会更易读。 - Sam

4
您可以这样做:
function concatRegex(...segments) {
  return new RegExp(segments.join(''));
}

段落中的片段将作为字符串(而不是正则表达式字面量)作为单独的参数传递。

2

使用带有两个参数的构造函数,避免尾部斜杠的问题:

var re_final = new RegExp("\\" + ".", "g");    // constructor can have 2 params!
console.log("...finally".replace(re_final, "!") + "\n" + re_final + 
    " works as expected...");                  // !!!finally works as expected

                         // meanwhile

re_final = new RegExp("\\" + "." + "g");              // appends final '/'
console.log("... finally".replace(re_final, "!"));    // ...finally
console.log(re_final, "does not work!");              // does not work

1

更简单的方法是连接源代码,例如:

a = /\d+/
b = /\w+/
c = new RegExp(a.source + b.source)

c 值的结果将是:

/\d+\w+/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接