【问题标题】:Template literal inside of the RegExRegEx 中的模板文字
【发布时间】:2021-11-24 00:00:59
【问题描述】:

我试图在 RegEx 中放置一个模板文字,但它不起作用。然后我创建了一个变量 regex 来保存我的正则表达式,但它仍然没有给我想要的结果。

但是,如果我单独 console.log(regex),我确实收到了所需的正则表达式,例如 /.+?(?=location)/i/.+?(?=date)/i 等,但是一旦我将 regex 放在 .replace 内,它似乎不起作用

function validate (data) {
  let testArr = Object.keys(data);
  errorMessages.forEach((elem, i) => {
    const regex = `/.+?(?=${elem.value})/i`;
    const a = testArr[i].replace(regex, '');
    })
  }

【问题讨论】:

    标签: javascript regex template-literals


    【解决方案1】:

    您的regex 变量是一个字符串。要使其成为 RegExp,请使用 RegExp 构造函数:

    const regex = new RegExp(String.raw`pattern_as_in_regex_literal_without_delimiters`)
    

    例如,像/<\d+>/g 这样的正则表达式文字可以重写为

    const re = RegExp(String.raw`<\d+>`, 'g') // One \ is a literal backslash
    const re = RegExp(`<\\d+>`, 'g')       // Two \ are required in a non-raw string literal
    

    要插入一个你可以使用的变量

    const digits = String.raw`\d+`;
    const re = RegExp(`<${digits}>`, 'g')
    

    为了解决您的问题,您可以使用

    const regex = new RegExp(`.+?(?=${elemvalue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')})`, "i"); 
    

    另外,在正则表达式中加入escape the variable 是个好主意,这样所有特殊的正则表达式元字符都被视为文字。

    const s = "final (location)";
    const elemvalue = "(location)";
    const regex = new RegExp(`.+?(?=${elemvalue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')})`, "i");
    // console.log(regex); // /.+?(?=\(location\))/i
    // console.log(typeof(regex)); // object
    let a = s.replace(regex, '');
    console.log(a);

    【讨论】:

      【解决方案2】:

      模板文字的更高级形式是Tagged templates。 -MDN

      const escape = s => `${s}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
      
      const regex = ({ // (1)
        raw: [part, ...parts]
      }, ...subs) => new RegExp(
        subs.reduce( // (2)
          (result, sub, i) => `${result}${escape(sub)}${parts[i]}`,
          part
        )
      );
      
      
      const t1 = `d`;
      const r1 = regex `^ab(c${t1}e)`;  // (3) 
      // /^ab(cde)/
      console.log('r2.test(`abc${t1}e`); ➜', r1.test(`abc${t1}e`)); // true
      
      // Check for proper escaped special chars
      const t2 = `(:?bar)\d{2}`;
      const r2 = regex `foo${t2}`; 
      // /foo\(:\?bar\)d\{2\}/  ➜ t2 is escaped!
      
      console.log('r2.test(`foo${t2}`); ➜', r2.test(`foo${t2}`)); // true
      console.log('r2.test(`foo\(:\?bar\)d\{2\}`); ➜', r2.test(`foo\(:\?bar\)d\{2\}`)); // true
      console.log('r2.test(`foobar11`); ➜', r2.test(`foobar11`)); // false
      console.log(r2);

      工作原理:

      1. 定义一个“标签函数”(名称无关紧要),例如regex()模板文字字符串 构建我们的正则表达式 (return new RegExp())。

        提示:标记函数甚至不需要返回字符串!

        • 第一个参数partsstring[](注入变量之间的段)。

          a${..}bc${..}de['a', 'bc', 'de'] = parts

          提示:special raw 属性可用于标记函数的 first 参数,允许您在输入原始字符串时访问它们,而无需处理转义序列。

          这就是我们需要的!我们可以解构 first 参数,并拆分出parts 的第一个part。这是我们的第一个正则表达式 result 段。

        • subs... 是注入变量的替换值:

          ..${'x'}..${'y'}..['x', 'y']

      2. 循环...subs上并连接正则表达式字符串

        1. escape() 来自当前 stubs[i] 的特殊字符(这会破坏最终表达式)。
        2. 追加转义 stubs[i] 并在其间添加parts[i]
      3. 前缀一个模板文字字符串,带有regex `...` ,并返回一个新的RegExp

        const r1 = regex `^ab(c${t1}e)` /^ab(cde)/


      条件转义

      到目前为止,regex() 将所有模板变量 ..${..}..${..}.. 处理为字符串。 (调用.toString()

      regex `X${new Date()}X`          // /X7\.12\.2020X/
      regex `X${{}}X`                  // /X\[object Object\]X/
      regex `X${[1, 'b', () => 'c']}X` // /X1,b,\(\) => 'c'X/
      

      即使这是预期的行为,我们也不能在所有地方都使用它。

      如何在“标记模板”中连接正则表达式?

      用例:您需要一个“RegExp 工厂类”来从较小的部分构建更复杂的表达式。 例如。用于解析/验证 javascript-like MIME typesContent-Type 标头值的正则表达式。语法:media-type = type "/" subtype。 这就是我们想要找到的:

      • */*
      • application/*, application/javascript, application/ecmascript,
      • text/*, text/javascript, text/ecmascript
      const group = (name, regExp) => regex`(?<${name}>${regExp})`;
      
      const rTypeGroup = group(`type`, `(text|application|\*)+?`);  
      // /(?<type>\(text\|application\|\*\)\+\?)/ 
      const rSubGroup = group(`sub`, `((java|ecma)+?script|\*)+?`); 
      // /(?<sub>\(\(java\|ecma\)\+\?script\|\*\)\+\?)/
      // .. and test
      rTypeGroup.test(`application`);                   // false !!!
      rTypeGroup.test(`\(text\|application\|\*\)\+\?`); // true !!!
      

      由于regex() 避开了所有替换,我们的捕获组的主体在字面上匹配。对于某些类型,我们可以修改regex() 并跳过escape()。现在我们可以传递 RegEx 实例并跳过 escape()

      const escape = s => `${s}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
      const regex = ({
        raw: [part, ...parts]
      }, ...subs) => new RegExp( //            skip escape()
        subs.reduce( //                   ┏━━━━━━━━━┻━━━━━━━━━┓ 
          (result, sub, i) => `${result}${sub instanceof RegExp ? sub.source : escape(sub)}${parts[i]}`,
          part
        )
      );
      
      const group = (name, regExp) => regex `(?<${name}>${regExp})`;
      
      //                                         RegEx
      //                               ┏━━━━━━━━━━━┻━━━━━━━━━━━┓               
      const rTypeGroup = group(`type`, /(text|application|\*)+?/); //  /(?<type>(text|application|\*)+?)/
      const rSubGroup = group(`sub`, /((java|ecma)+?script|\*)+?/); // /(?<sub>((java|ecma)+?script|\*)+?)/
      
      // Type
      console.log('rTypeGroup.test(`*`); ➜', rTypeGroup.test(`*`)); // true
      console.log('rTypeGroup.test(`text`); ➜', rTypeGroup.test(`text`)); // true
      
      console.log('rTypeGroup.exec(`*`).groups.type; ➜', rTypeGroup.exec(`*`).groups.type); // '*'
      console.log('rTypeGroup.exec(`text`).groups.type; ➜', rTypeGroup.exec(`text`).groups.type); // 'text'
      
      // SubType
      console.log('rSubGroup.test(`*`); ➜', rSubGroup.test(`*`)); // true
      console.log('rSubGroup.test(`javascript`); ➜', rSubGroup.test(`javascript`)); // true
      console.log('rSubGroup.test(`ecmascript`); ➜', rSubGroup.test(`ecmascript`)); // true
      
      console.log('rSubGroup.exec(`*`).groups.sub; ➜', rSubGroup.exec(`*`).groups.sub); // '*'
      console.log('rSubGroup.exec(`javascript`).groups.sub; ➜', rSubGroup.exec(`javascript`).groups.sub); // 'javascript'

      我们现在可以决定是否应该转义 var。由于我们忽略了任何量词和标志,我们的小组还验证了ABCtextDEF12texttext、...。

      现在我们可以捆绑多个 RegEx:

      //                                                 '/' would be escaped! RegEx needed..            
      //                                                               ┏━┻━┓
      const rMediaTypeGroup = group(`mediaType`, regex `${rTypeGroup}${/\//}${rSubGroup}`);
      // /(?<mediaType>(?<type>(text|application|\*)+?)\/(?<sub>((java|ecma)+?script|\*)+?))/
      rMediaTypeGroup.test(`text/javascript`)); // true
      

      const escape = s => `${s}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
      const regex = ({
        raw: [part, ...parts]
      }, ...subs) => new RegExp(
        subs.reduce(
          (result, sub, i) => `${result}${sub instanceof RegExp ? sub.source : escape(sub)}${parts[i]}`,
          part
        )
      );
      
      const group = (name, regExp) => regex `(?<${name}>${regExp})`;
      const rTypeGroup = group(`type`, /(text|application|\*)+?/);
      const rSubGroup = group(`sub`, /((java|ecma)+?script|\*)+?/);
      
      //                                                 '/' would be escaped! RegEx needed..            
      //                                                               ┏━┻━┓
      const rMediaTypeGroup = group(`mediaType`, regex `${rTypeGroup}${/\//}${rSubGroup}`);
      // /(?<mediaType>(?<type>(text|application|\*)+?)\/(?<sub>((java|ecma)+?script|\*)+?))/
      
      console.log('rMediaTypeGroup.test(`*/*`); ➜', rMediaTypeGroup.test(`*/*`)); // true
      console.log('rMediaTypeGroup.test(`**/**`); ➜', rMediaTypeGroup.test(`**/**`)); // true
      console.log('rMediaTypeGroup.test(`text/javascript`); ➜', rMediaTypeGroup.test(`text/javascript`)); // true
      
      console.log('rMediaTypeGroup.test(`1text/javascriptX`); ➜', rMediaTypeGroup.test(`1text/javascriptX`)); // true
      console.log('rMediaTypeGroup.test(`*/java`); ➜', rMediaTypeGroup.test(`*/java`)); // true
      
      console.log('rMediaTypeGroup.test(`text/X`); ➜', rMediaTypeGroup.test(`text/X`)); // false
      console.log('rMediaTypeGroup.test(`/*`); ➜', rMediaTypeGroup.test(`/*`)); // false

      标志

      在初始化之前必须知道所有标志。您可以阅读它们(/xx/gm.flags/xx/gm.multiline/xx/i.ignoreCase,..),但没有二传手。

      返回new Regex() 的标记模板函数(例如regex())需要知道所有标志。

      本节演示了如何处理标志的 3 种替代方法。

      • 选项 A:将标志传递为 ${templateVariable} ➜ 不推荐!
      • 选项 B:扩展 class RegExp
      • 选项 C:使用 Proxy() 作为标志

      选项 A:将标志传递为 ${templateVariable}。 ➜ 不推荐!

      像其他替换变量一样的线程标志。我们需要检查最后一个变量是否是 flag-like (g, mi,..) 并将其从subs...中拆分出来...

      const escape = s => `${s}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
      
      const regex = ({
        raw: [part, ...parts],
        // super verbose...
        splitFlags = ({ // destruct subs[] Array (arrays are objects!)
          length: l,    // destruct subs[].length to l
          iEnd = l - 1, // last array index to iEnd
          [iEnd]: sub,  // subs[subs.length - i] to sub
          ...subs       // subs[0...n-1] to subs
        }) => [         // returns RegEx() constr. params: [flags, concat regex string]                                                    
          //             ┏━━━━━━━━ all chars of sub flag-like? ━━━━━━━━━┓   ┏━ flags  ┏━ re-add last sub and set flags: undefined
          [...sub].every(f => ['s', 'g', 'i', 'm', 'y', 'u'].includes(f)) ? sub : !(subs[iEnd] = sub) || undefined,
          Object.values(subs).reduce( // concat regex string
            (result, sub, i) => `${result}${escape(sub)}${parts[i]}`,
            part
          )
        ]
      }, ...subs) => new RegExp(...splitFlags(subs).reverse());
      
      const r1 = regex `^foo(${`bar`})${'i'}`;
      console.log('r1:', r1, 'flags:', r1.flags); // /^foo(bar)/i    ['i' flag]
      
      const r2 = regex `^foo(${`bar`})${'mgi'}`;
      console.log('r2:', r2, 'flags:', r2.flags); // /^foo(bar)/gim  ['gim' flag]
      
      //            invalid flag 'x' ━━━━┓
      const r3 = regex `^foo(${`bar`})${'x'}`;
      console.log('r3:', r3, 'flags:', r3.flags); // /^foo(bar)x/    [no flags]
      
      //              invalid flag 'z' ━━━━┓
      const r4 = regex `^foo(${`bar`})${'gyzu'}`;
      console.log('r4:', r4, 'flags:', r4.flags); // /^foo(bar)gyzu/ [no flags]

      代码看起来超级冗长,并且从外部混淆 flag-logicsubstitution-logic 并不明显。如果最后一个变量被错误地确定为 flag-like,它也会破坏最终的正则表达式。

      我们查找电话类型,例如 i-phonea-phone、...

      const rPhoneA = regex `${`a`}-phone`; // 
      console.log('rPhoneA:', rPhoneA, 'flags:', rPhoneA.flags); // /a-phone/     [no flags]
      const rPhoneI = regex `${`i`}-phone`; 
      console.log('rPhoneI:', rPhoneI, 'flags:', rPhoneI.flags); // /(?:)/i       ['i' flag]
      
      console.log('rPhoneA.test(`a-phone`); ➜', rPhoneA.test(`a-phone`)); // true
      console.log('rPhoneA.test(`i-phone`); ➜', rPhoneA.test(`i-phone`)); // false
      
      console.log('rPhoneI.test(`a-phone`); ➜', rPhoneI.test(`a-phone`)); // true
      console.log('rPhoneI.test(`i-phone`); ➜', rPhoneI.test(`i-phone`)); // true
      

      ...所有电话都是i-phones!因为i 是一个flag-like 替代品,并且已从subs... 数组中移除,该数组现在为空[]reduce() 返回一个空字符串 ''new RegExp('', 'i') 添加一个空的非捕获组:(?:)

      选项 B:扩展 class RegExp

      我们可以扩展 RegExp 并添加 getter 方法来设置标志。让它们自动返回,这样我们就可以将它们链接起来。我们甚至可以添加一个_ "clear flags" 方法。

      这很好用,但也有效果,每个添加/删除的标志都会导致TRegExp 的新克隆。如果我们构建“静态”(可缓存)表达式,对您来说可能没问题。

      • 答:在循环内的构造函数中添加getters。或者..
      • B:添加类 getters

      const escape = s => `${s}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
      
      class TRegExp extends RegExp {
      
        constructor(...args) {
          super(...args);
      
          // Clear all flags
          Object.defineProperty(this, '_', {
            get() {
              return this.flags.length ? new TRegExp(this.source) : this;
            },
            enumerable: false
          });
      
          // A: define getters for all flags  
          ['g', 'i', 'm', 'u', 'y'].reduce((my, flag) => Object.defineProperty(my, flag, {
            get() {    // clone this on flags change ━━┓
              return my.flags.includes(flag) ? my : new TRegExp(my.source, `${my.flags}${flag}`);
            }, //                 return this ━━┛
            enumerable: false
          }), this);
        }
      
        // B: Define getters for each flag individually
        // get g() {
        //     return this.flags.includes('g') ? this : new TRegExp(this.source, `${this.flags}g`);
        // }
      }
      
      const regex = ({raw: [part, ...parts]}, ...subs) => new TRegExp(
        subs.reduce( //                                       ┣━━ TRegExp()
          (result, sub, i) => `${result}${subs instanceof TRegExp ? sub.source : escape(sub)}${parts[i]}`,
          part
        )
      );
      
      console.log('TRegExp +flags:', regex `foo(bar)`.g);       // /foo(bar)/g
      console.log('TRegExp +flags:', regex `foo(bar)`.i.m);     // /foo(bar)/im
      console.log('TRegExp +flags:', regex `foo(bar)`.g.i._.y); // /foo(bar)/y 
      //                                              ┗━━━━━┻━━━━ ( + 'g', + 'i', - 'gi', + 'y')
      
      const group = regex `(?<foo>(:?bar)\d{2})`.g.i;
      const t = `a bar12 bar0 bar13 bar-99 xyz BaR14 bar15 abc`;
      console.log([...t.matchAll(group)].map(m => m.groups.foo));
      // ["bar12", "bar13", "BaR14", "bar15"]

      选项 C:使用 Proxy() 作为标志

      您可以代理“标记模板函数”,例如regex() 并拦截任何get() 就可以了。代理可以解决许多问题,但也可能导致极大的混乱。您可以重写整个函数并完全改变初始行为。

      const escape = s => `${s}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
      
      const _regex = (flags, {raw: [part, ...parts]}, ...subs) => new RegExp(
        subs.reduce(
          (result, sub, i) => `${result}${sub instanceof RegExp ? sub.source : escape(sub)}${parts[i]}`,
          part
        ),
        flags
      ); // ┗━━━ flags
      
      const regex = new Proxy(_regex.bind(undefined, ''), {
        get: (target, property) => _regex.bind(undefined, property)
      });
      
      
      console.log('Proxy +flags:', regex.gi `foo(bar)`); // /foo(bar)/gi
      
      const r = /(:?bar)\d{2}/; // matches: 'bar' + digit + digit ➜ 'bar12', 'abar123',..
      const t = `(:?bar)\d{2}`; // template literal with regExp special chars
      
      console.log('Proxy +flags:', regex.gi `(foo${r})`); // /(foo(:?bar)\d{2})/gi
      console.log('Proxy +flags:', regex.gi `(foo${t})`); // /(foo\(:\?bar\)d\{2\})/gi
      //                         flags ━┻━┛

      阅读更多

      【讨论】:

      • 这读起来更像是一篇博文,而不是适合 StackOverflow 的内容,而且它似乎并没有真正回答这个问题。
      猜你喜欢
      • 2017-08-21
      • 1970-01-01
      • 1970-01-01
      • 2019-12-13
      • 2018-09-18
      • 1970-01-01
      • 2023-01-18
      • 1970-01-01
      相关资源
      最近更新 更多