阿波罗的战车
const escapeRegex = s => s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");const extract = (start, end, str) => Array.from( str.matchAll(`(.+?)(${escapeRegex(start)}|${escapeRegex(end)}|$)`), ([, text, mark]) => ({ marker: mark === end, value: text.trim() }));console.log(extract( "{startMarker}", "{endMarker}", "This is {startMarker} the string {endMarker} for {startMarker} example. {endMarker}"));解释正则表达式我们发送以两个标记之一结尾的文本段。我们可以提取包括标记在内的每个部分。This is {startMarker} the string {endMarker} ^______^^___________^^__________^^_________^| text mark || text mark |^___________________^^_____________________^ section section文本将成为value结果对象的文本,可以检查标记段是否是{endMarker}为了生成true或false用于结果对象。因此,如果我们能够正确提取段和节,结果是:result = { marker: marker === "{endMarker}", value: text.trim()}可以为我们执行此操作的正则表达式是:/(.+?)(\{startMarker\}|\{endMarker\}|$)/g请参阅 Regex101(.+?)将匹配并捕获文本段(\{startMarker\}|\{endMarker\}|$)将匹配并提取文本段末尾的标记。它还匹配行尾,以防最后一个标记后有更多文本,就像您有for {startMarker} example. {endMarker} more text here一代更一般地说,我们可以采用任何字符串作为开始和结束标记,然后对它们进行转义以确保它们字面匹配,即使其中存在像.或 之类的元字符*。const escapeRegex = s => s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");这样我们就可以将startand作为字符串并使用构造函数生成end正则表达式:RegExpconst escapeRegex = s => s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");const start = "{startMarker}";const end = "{endMarker}";const regex = new RegExp(`(.+?)(${escapeRegex(start)}|${escapeRegex(end)}|$)`, "g");console.log(regex.toString());匹配该String#matchAll方法将生成一个迭代器,其中包含应用于字符串的正则表达式的所有匹配项。const escapeRegex = s => s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");const extract = (start, end, str) => { const sequence = str.matchAll(`(.+?)(${escapeRegex(start)}|${escapeRegex(end)}|$)`); for(const result of sequence) { console.log(result); }};extract( "{startMarker}", "{endMarker}", "This is {startMarker} the string {endMarker} for {startMarker} example. {endMarker}");该.matchAll()方法接受字符串作为参数,并使用RegExp构造函数自动将其转换为正则表达式,并进一步自动添加全局标志。然而,TypeScript 目前似乎不允许这样做 - 该方法的类型只允许一个RegExp对象,因此仅对于 TypeScript(直到类型修复)你必须调用str.matchAll(new RegExp(`(.+?)(${escapeRegex(start)}|${escapeRegex(end)}|$)`, "g"))转换为数组将可迭代对象转换为数组的最简单方法是使用Array.from. 它采用的第一个参数可以是可迭代的,并且会自动转换为数组。第二个参数是在将每个元素放入数组之前应用的映射函数。由于我们收到正则表达式匹配结果,我们可以使用此函数将它们直接转换为所需的项目:result => { const match = result[1]; const marker = result[2]; return { marker: marker === end, value: match.trim() };}这给了我们更详细的版本:const escapeRegex = s => s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");const extract = (start, end, str) => { return Array.from( str.matchAll(`(.+?)(${escapeRegex(start)}|${escapeRegex(end)}|$)`), result => { const match = result[1]; const marker = result[2]; return { marker: marker === end, value: match.trim() }; } );}console.log(extract( "{startMarker}", "{endMarker}", "This is {startMarker} the string {endMarker} for {startMarker} example. {endMarker}"));游乐场链接然而,我们可以通过解构来减少所需的代码,它就变成了。([, text, mark]) => ({ marker: mark === end, value: text.trim()})这最终为我们提供了顶部的初始代码(再次包含在内,以避免向上滚动):const escapeRegex = s => s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");const extract = (start, end, str) => Array.from( str.matchAll(`(.+?)(${escapeRegex(start)}|${escapeRegex(end)}|$)`), ([, text, mark]) => ({ marker: mark === end, value: text.trim() }));console.log(extract( "{startMarker}", "{endMarker}", "This is {startMarker} the string {endMarker} for {startMarker} example. {endMarker}"));关于 ES2020 兼容性的最后说明String#matchAll来自 ES2020 规范。如果您当前没有瞄准该目标并且不想这样做,您可以使用工作方式非常相似的生成器函数轻松推出自己的版本:function* matchAll(pattern, text) { const regex = typeof pattern === "string" ? new RegExp(pattern, "g") //convert to global regex : new RegExp(pattern); //or make a copy of the regex object to avoid mutating the input let result; while(result = regex.exec(text)) //apply `regex.exec` repeatedly yield result; //and produce each result from the iterator}这里唯一值得注意的遗漏是,String#matchAll如果传入非全局正则表达式对象,则会抛出错误。它仍然可以实现,但我使用了一个稍短的实现来进行说明。使用自定义,matchAll您可以定位 ES2020 之前的版本,而无需填充const escapeRegex = s => s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");function* matchAll(pattern, text) { const regex = typeof pattern === "string" ? new RegExp(pattern, "g") : new RegExp(pattern); let result; while(result = regex.exec(text)) yield result;}const extract = (start, end, str) => Array.from( matchAll(`(.+?)(${escapeRegex(start)}|${escapeRegex(end)}|$)`, str), ([, text, mark]) => ({ marker: mark === end, value: text.trim() }));console.log(extract( "{startMarker}", "{endMarker}", "This is {startMarker} the string {endMarker} for {startMarker} example. {endMarker}"));