如何在 JavaScript 中将一个长的正则表达式分割成多行?

我有一个非常长的正则表达式,我希望在 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,}))$/;
72242 次浏览

[ 编辑2022/08]创建一个小的 Github 存储库来创建带有空格、注释和模板的正则表达式。


您可以将其转换为字符串并通过调用 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. 在将 字面意思转换为字符串时,您需要转义所有的反斜杠,因为在计算 字符串时会消耗反斜杠。(更多细节见加代的评论。)

  2. RegExp接受修饰符作为第二个参数

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

[ 附加 ES20xx(带标签的模板)]

在 ES20xx 中可以使用 带标签的模板

注:

  • 这里的缺点是不能在正则表达式字符串中使用纯空格(始终使用 \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);
})();

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

/\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 + ")$");

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$/
]);

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

扩展@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 => r.source)

上面的正则表达式缺少一些不能正常工作的黑色斜杠。我编辑了正则表达式。请考虑这个正则表达式的工程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(''));

要避免使用 Array 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,}))$');

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

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);

这里有很好的答案,但是为了完整起见,有人应该提到 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

感谢神奇的 模板文字世界,您现在可以在 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}/

那多线呢?

'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吧!
在我的火狐和 Chrome 上工作。


好吧“来点更复杂的怎么样?”
当然,这是我正在研究的解析 JS 解析器的对象的一部分:

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 上测试过,非常棒!

如果 好奇你能不能看看我在做什么它的演示
虽然它只能在 Chrome 上运行,因为 Firefox 不支持回溯引用或命名组。因此,请注意,这个答案中给出的例子实际上是一个中性版本,可能很容易受骗接受无效的字符串。

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

要使用这个代码片段,您需要调用可变参数函数 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, ...])。只要对象只包含一个键,键的内容可以是任何内容。请注意,如果第一个字符可以被解释为量词,则必须使用 ]作为虚拟开头,而不是 ()。也就是说 /[+?]/变成了 {"":[/]+?/]}

下面是一段代码和一个更完整的示例:

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
);

@ 哈什布朗的 回答让我走上了正确的道路。这是我的版本,也受到了这个 博客的启发。

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