【问题标题】:How to split a long regular expression into multiple lines in JavaScript?如何在 JavaScript 中将长正则表达式拆分为多行?
【发布时间】:2018-02-05 01:08:53
【问题描述】:

我有一个很长的正则表达式,我希望在我的 JavaScript 代码中将其拆分为多行,以根据 JSLint 规则保持每行长度为 80 个字符。我认为它更适合阅读。 这是模式示例:

var pattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

【问题讨论】:

  • 您似乎正在(试图)验证电子邮件地址。为什么不干脆做/\S+@\S+\.\S+/
  • 您可能应该寻找一种不使用正则表达式或使用多个较小正则表达式的方法。这将比那么长的正则表达式更具可读性。如果您的正则表达式超过 20 个字符,那么可能有更好的方法。
  • 现在宽屏显示器不是80个字符有点过时了吗?
  • @OlegV.Volkov 不。一个人可能在 vim 中使用拆分窗口,服务器机房中的虚拟终端。假设每个人都将在与您相同的视口中编码是错误的。此外,将行数限制为 80 个字符会迫使您将代码分解为更小的函数。
  • 好吧,我当然看到了你想要在这里做这件事的动机——一旦这个正则表达式被分成多行,正如 Koolilnc 所展示的那样,它立即成为可读的、自我记录的代码的完美示例。 ¬_¬

标签: javascript regex jslint expression readability


【解决方案1】:

扩展@KooiInc 答案,您可以通过使用RegExp 对象的source 属性来避免手动转义每个特殊字符。

例子:

var urlRegex= new RegExp(''
  + /(?:(?:(https?|ftp):)?\/\/)/.source     // protocol
  + /(?:([^:\n\r]+):([^@\n\r]+)@)?/.source  // user:pass
  + /(?:(?:www\.)?([^\/\n\r]+))/.source     // domain
  + /(\/[^?\n\r]+)?/.source                 // request
  + /(\?[^#\n\r]*)?/.source                 // query
  + /(#?[^\n\r]*)?/.source                  // anchor
);

或者,如果您想避免重复 .source 属性,您可以使用 Array.map() 函数:

var urlRegex= new RegExp([
  /(?:(?:(https?|ftp):)?\/\/)/      // protocol
  ,/(?:([^:\n\r]+):([^@\n\r]+)@)?/  // user:pass
  ,/(?:(?:www\.)?([^\/\n\r]+))/     // domain
  ,/(\/[^?\n\r]+)?/                 // request
  ,/(\?[^#\n\r]*)?/                 // query
  ,/(#?[^\n\r]*)?/                  // anchor
].map(function(r) {return r.source}).join(''));

在 ES6 中,map 函数可以简化为: .map(r =&gt; r.source)

【讨论】:

  • 正是我想要的,超级干净。谢谢!
  • 这对于将 cmets 添加到长正则表达式非常方便。但是,它受到在同一行上匹配括号的限制。
  • 绝对是这个!超级好,能够评论每个子正则表达式。
  • 谢谢,它有助于将源代码放入正则表达式函数中
  • 非常聪明。谢谢,这个想法对我帮助很大。顺便说一句:我将整个内容封装在一个函数中以使其更加简洁:combineRegex = (...regex) =&gt; new RegExp(regex.map(r =&gt; r.source).join("")) 用法:combineRegex(/regex1/, /regex2/, ...)
【解决方案2】:

您可以将其转换为字符串并通过调用 new RegExp() 创建表达式:

var myRE = new RegExp (['^(([^<>()[\]\\.,;:\\s@\"]+(\\.[^<>(),[\]\\.,;:\\s@\"]+)*)',
                        '|(\\".+\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                        '[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\\.)+',
                        '[a-zA-Z]{2,}))$'].join(''));

注意事项:

  1. 表达式文字转换为字符串时,您需要转义所有反斜杠,因为在评估字符串文字时会使用反斜杠。 (有关详细信息,请参阅 Kayo 的评论。)
  2. RegExp 接受修饰符作为第二个参数

    /regex/g => new RegExp('regex', 'g')

[添加 ES20xx(标记模板)]

在 ES20xx 中你可以使用tagged templates。见sn-p。

注意:

  • 这里的缺点是您不能在正则表达式字符串中使用纯空格(始终使用\s\s+\s{1,x}\t\n 等)。

(() => {
  const createRegExp = (str, opts) => 
    new RegExp(str.raw[0].replace(/\s/gm, ""), opts || "");
  const yourRE = createRegExp`
    ^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|
    (\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|
    (([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`;
  console.log(yourRE);
  const anotherLongRE = createRegExp`
    (\byyyy\b)|(\bm\b)|(\bd\b)|(\bh\b)|(\bmi\b)|(\bs\b)|(\bms\b)|
    (\bwd\b)|(\bmm\b)|(\bdd\b)|(\bhh\b)|(\bMI\b)|(\bS\b)|(\bMS\b)|
    (\bM\b)|(\bMM\b)|(\bdow\b)|(\bDOW\b)
    ${"gi"}`;
  console.log(anotherLongRE);
})();

【讨论】:

  • new RegExp 是多行正则表达式的好方法。您可以使用字符串连接运算符代替连接数组:var reg = new RegExp('^([a-' + 'z]+)$','i');
  • 注意: 使用上述答案,一个长的正则表达式文字可以分成多行。但是它需要小心,因为您不能简单地复制正则表达式文字(使用// 定义)并将其作为字符串参数粘贴到 RegExp 构造函数。这是因为在评估 字符串文字 时会消耗反斜杠字符。示例:/Hey\sthere/ 不能替换为 new RegExp("Hey\sthere")。相反,它应该替换为new RegExp("Hey\\sthere") 注意额外的反斜杠!因此,我更喜欢在一条长线上留下一个长的正则表达式文字
  • 一个更清晰的方法是创建包含有意义的小节的命名变量,并将那些作为字符串或数组连接起来。这使您可以以更容易理解的方式构建RegExp
  • 另外,MDN 建议在正则表达式保持不变时使用文字表示法,而在正则表达式可以更改时使用构造函数表示法。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
【解决方案3】:

new RegExp 中使用字符串很尴尬,因为您必须转义所有反斜杠。您可以编写较小的正则表达式并将它们连接起来。

让我们拆分这个正则表达式

/^foo(.*)\bar$/

以后我们会用一个函数让事情变得更漂亮

function multilineRegExp(regs, options) {
    return new RegExp(regs.map(
        function(reg){ return reg.source; }
    ).join(''), options);
}

现在让我们摇滚吧

var r = multilineRegExp([
     /^foo/,  // we can add comments too
     /(.*)/,
     /\bar$/
]);

因为它有成本,所以尝试只构建一次真正的正则表达式,然后使用它。

【讨论】:

  • 这很酷——不仅您不必进行额外的转义,而且您还保留了子正则表达式的特殊语法高亮!
  • 一个警告:您需要确保您的子正则表达式是独立的,或者将每个子正则表达式包装在一个新的括号组中。示例:multilineRegExp([/a|b/, /c|d]) 结果为 /a|bc|d/,而您的意思是 (a|b)(c|d)
【解决方案4】:

这里有很好的答案,但为了完整起见,有人应该用prototype chain 提及 Javascript 的核心继承特性。这样的事情说明了这个想法:

RegExp.prototype.append = function(re) {
  return new RegExp(this.source + re.source, this.flags);
};

let regex = /[a-z]/g
.append(/[A-Z]/)
.append(/[0-9]/);

console.log(regex); //=> /[a-z][A-Z][0-9]/g

【讨论】:

  • 这是最好的答案。
【解决方案5】:

感谢template literals 的奇妙世界,您现在可以在 ES6 中编写大型、多行、注释良好、甚至语义嵌套的正则​​表达式。

//build regexes without worrying about
// - double-backslashing
// - adding whitespace for readability
// - adding in comments
let clean = (piece) => (piece
    .replace(/((^|\n)(?:[^\/\\]|\/[^*\/]|\\.)*?)\s*\/\*(?:[^*]|\*[^\/])*(\*\/|)/g, '$1')
    .replace(/((^|\n)(?:[^\/\\]|\/[^\/]|\\.)*?)\s*\/\/[^\n]*/g, '$1')
    .replace(/\n\s*/g, '')
);
window.regex = ({raw}, ...interpolations) => (
    new RegExp(interpolations.reduce(
        (regex, insert, index) => (regex + insert + clean(raw[index + 1])),
        clean(raw[0])
    ))
);

使用它,您现在可以像这样编写正则表达式:

let re = regex`I'm a special regex{3} //with a comment!`;

输出

/I'm a special regex{3}/

Or what about multiline?

'123hello'
    .match(regex`
        //so this is a regex

        //here I am matching some numbers
        (\d+)

        //Oh! See how I didn't need to double backslash that \d?
        ([a-z]{1,3}) /*note to self, this is group #2*/
    `)
    [2]

输出hel,整洁!
“如果我需要实际搜索换行符怎么办?”,然后使用\n silly!
在我的 Firefox 和 Chrome 上工作。


好的,“稍微复杂一点的东西怎么样?”
当然,here's a piece of an object destructuring JS parser I was working on

regex`^\s*
    (
        //closing the object
        (\})|

        //starting from open or comma you can...
        (?:[,{]\s*)(?:
            //have a rest operator
            (\.\.\.)
            |
            //have a property key
            (
                //a non-negative integer
                \b\d+\b
                |
                //any unencapsulated string of the following
                \b[A-Za-z$_][\w$]*\b
                |
                //a quoted string
                //this is #5!
                ("|')(?:
                    //that contains any non-escape, non-quote character
                    (?!\5|\\).
                    |
                    //or any escape sequence
                    (?:\\.)
                //finished by the quote
                )*\5
            )
            //after a property key, we can go inside
            \s*(:|)
      |
      \s*(?={)
        )
    )
    ((?:
        //after closing we expect either
        // - the parent's comma/close,
        // - or the end of the string
        \s*(?:[,}\]=]|$)
        |
        //after the rest operator we expect the close
        \s*\}
        |
        //after diving into a key we expect that object to open
        \s*[{[:]
        |
        //otherwise we saw only a key, we now expect a comma or close
        \s*[,}{]
    ).*)
$`

它输出/^\s*((\})|(?:[,{]\s*)(?:(\.\.\.)|(\b\d+\b|\b[A-Za-z$_][\w$]*\b|("|')(?:(?!\5|\\).|(?:\\.))*\5)\s*(:|)|\s*(?={)))((?:\s*(?:[,}\]=]|$)|\s*\}|\s*[{[:]|\s*[,}{]).*)$/

并通过一个小演示运行它?

let input = '{why, hello, there, "you   huge \\"", 17, {big,smelly}}';
for (
    let parsed;
    parsed = input.match(r);
    input = parsed[parsed.length - 1]
) console.log(parsed[1]);

成功输出

{why
, hello
, there
, "you   huge \""
, 17
,
{big
,smelly
}
}

注意成功捕获引用的字符串。
我在 Chrome 和 Firefox 上测试过,效果很好!

如果curious you can checkout what I was doing,和its demonstration
虽然它只适用于 Chrome,因为 Firefox 不支持反向引用或命名组。因此请注意,此答案中给出的示例实际上是一个中性版本,可能很容易被欺骗接受无效字符串。

【讨论】:

  • 你应该考虑把它导出为一个NodeJS包,太棒了
  • 虽然我自己从来没有做过,但这里有一个非常详尽的教程:zellwk.com/blog/publish-to-npm。我建议在页面末尾检查 np。我从来没用过,但辛德雷·索胡斯是个魔术师,所以我不会放弃它。
  • 嘿@Hashbrown,你介意我把它做成一个包吗?我当然会给你归属
  • @Siddharth 去吧。我似乎还没有解决它。 Hashbrown777也在github上
【解决方案6】:

上面的正则表达式缺少一些不能正常工作的黑色斜线。所以,我编辑了正则表达式。请考虑这个 99.99% 用于电子邮件验证的正则表达式。

let EMAIL_REGEXP = 
new RegExp (['^(([^<>()[\\]\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\.,;:\\s@\"]+)*)',
                    '|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                    '[0-9]{1,3}\])|(([a-zA-Z\\-0-9]+\\.)+',
                    '[a-zA-Z]{2,}))$'].join(''));

【讨论】:

    【解决方案7】:

    要避免数组join,您还可以使用以下语法:

    var pattern = new RegExp('^(([^<>()[\]\\.,;:\s@\"]+' +
      '(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@' +
      '((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' +
      '(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');
    

    【讨论】:

      【解决方案8】:

      你可以简单地使用字符串操作。

      var pattenString = "^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|"+
      "(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|"+
      "(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$";
      var patten = new RegExp(pattenString);
      

      【讨论】:

        【解决方案9】:

        我尝试通过封装所有内容并实现对拆分捕获组和字符集的支持来改进 korun 的答案 - 使这种方法更加通用。

        要使用这个sn-p,你需要调用可变参数函数combineRegex,它的参数是你需要组合的正则表达式对象。它的实现可以在底部找到。

        捕获组不能以这种方式直接拆分,因为它会使某些部分只留下一个括号。您的浏览器会因异常而失败。

        相反,我只是将捕获组的内容传递到一个数组中。 combineRegex 遇到数组时会自动添加括号。

        此外,量词需要遵循一些东西。如果由于某种原因需要在量词前面拆分正则表达式,则需要添加一对括号。这些将被自动删除。关键是一个空的捕获组是毫无用处的,这样量词就有了参考意义。相同的方法可用于非捕获组(/(?:abc)/ 变为 [/()?:abc/])。

        最好用一个简单的例子来解释:

        var regex = /abcd(efghi)+jkl/;
        

        会变成:

        var regex = combineRegex(
            /ab/,
            /cd/,
            [
                /ef/,
                /ghi/
            ],
            /()+jkl/    // Note the added '()' in front of '+'
        );
        

        如果您必须拆分字符集,您可以使用对象 ({"":[regex1, regex2, ...]}) 而不是数组 ([regex1, regex2, ...])。只要对象只包含一个键,键的内容可以是任何内容。请注意,如果第一个字符可以解释为量词,则必须使用 ] 作为虚拟开头,而不是 ()。 IE。 /[+?]/ 变为 {"":[/]+?/]}

        这里是 sn-p 和一个更完整的例子:

        function combineRegexStr(dummy, ...regex)
        {
            return regex.map(r => {
                if(Array.isArray(r))
                    return "("+combineRegexStr(dummy, ...r).replace(dummy, "")+")";
                else if(Object.getPrototypeOf(r) === Object.getPrototypeOf({}))
                    return "["+combineRegexStr(/^\]/, ...(Object.entries(r)[0][1]))+"]";
                else 
                    return r.source.replace(dummy, "");
            }).join("");
        }
        function combineRegex(...regex)
        {
            return new RegExp(combineRegexStr(/^\(\)/, ...regex));
        }
        
        //Usage:
        //Original:
        console.log(/abcd(?:ef[+A-Z0-9]gh)+$/.source);
        //Same as:
        console.log(
          combineRegex(
            /ab/,
            /cd/,
            [
              /()?:ef/,
              {"": [/]+A-Z/, /0-9/]},
              /gh/
            ],
            /()+$/
          ).source
        );

        【讨论】:

          【解决方案10】:

          @Hashbrown 的伟大 answer 让我走上了正轨。这是我的版本,也是受到 blog 的启发。

          function regexp(...args) {
            function cleanup(string) {
              // remove whitespace, single and multi-line comments
              return string.replace(/\s+|\/\/.*|\/\*[\s\S]*?\*\//g, '');
            }
          
            function escape(string) {
              // escape regular expression
              return string.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&');
            }
          
            function create(flags, strings, ...values) {
              let pattern = '';
              for (let i = 0; i < values.length; ++i) {
                pattern += cleanup(strings.raw[i]);  // strings are cleaned up
                pattern += escape(values[i]);        // values are escaped
              }
              pattern += cleanup(strings.raw[values.length]);
              return RegExp(pattern, flags);
            }
          
            if (Array.isArray(args[0])) {
              // used as a template tag (no flags)
              return create('', ...args);
            }
          
            // used as a function (with flags)
            return create.bind(void 0, args[0]);
          }
          

          像这样使用它:

          regexp('i')`
            //so this is a regex
          
            //here I am matching some numbers
            (\d+)
          
            //Oh! See how I didn't need to double backslash that \d?
            ([a-z]{1,3}) /*note to self, this is group #2*/
          `
          

          要创建这个RegExp 对象:

          /(\d+)([a-z]{1,3})/i
          

          【讨论】:

            【解决方案11】:

            就个人而言,我会选择一个不太复杂的正则表达式:

            /\S+@\S+\.\S+/
            

            当然,它比您当前的模式准确,但是您要完成什么?您是否正在尝试捕捉用户可能输入的意外错误,或者您是否担心您的用户可能会尝试输入无效地址?如果是第一个,我会选择更简单的模式。如果是后者,通过回复发送到该地址的电子邮件进行验证可能是更好的选择。

            但是,如果您想使用当前的模式,通过从较小的子模式构建它(IMO)会更容易阅读(和维护!),如下所示:

            var box1 = "([^<>()[\]\\\\.,;:\s@\"]+(\\.[^<>()[\\]\\\\.,;:\s@\"]+)*)";
            var box2 = "(\".+\")";
            
            var host1 = "(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])";
            var host2 = "(([a-zA-Z\-0-9]+\\.)+[a-zA-Z]{2,})";
            
            var regex = new RegExp("^(" + box1 + "|" + box2 + ")@(" + host1 + "|" + host2 + ")$");
            

            【讨论】:

            • 拒绝投票 - 尽管您关于降低正则表达式复杂性的 cmets 是有效的,但 OP 专门询问如何“将长正则表达式拆分为多行”。因此,尽管您的建议是有效的,但它是出于错误的原因给出的。例如更改业务逻辑以围绕编程语言工作。此外,您提供的代码示例非常丑陋。
            • @sleepycal 我认为 Bart 已经回答了这个问题。请参阅他答案的最后一部分。他已经回答了这个问题并给出了替代方案。
            猜你喜欢
            • 1970-01-01
            • 2011-12-21
            • 2020-12-31
            • 2021-04-17
            • 1970-01-01
            • 2022-08-06
            相关资源
            最近更新 更多