RegEx内部的模板文字

时间:2017-04-13 11:20:39

标签: javascript regex template-literals

我尝试在RegEx中放置模板文字,但它不起作用。然后我创建了一个变量regex来保存我的RegEx,但它仍然没有给我所需的结果。

但是如果我单独console.log(regex),我会收到所需的RegEx,例如/.+?(?=location)/i/.+?(?=date)/i等等,但是一旦我将regex放入.replace 1}}似乎无法正常工作

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

2 个答案:

答案 0 :(得分:14)

您的regex变量是 String 。要使其成为RegExp,请使用

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

答案 1 :(得分:0)

模板文字的更高级形式是 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

      提示:标记功能 first 参数上的 special raw属性可让您在输入原始字符串时访问它们,无需处理转义序列。

      这就是我们所需要的!我们可以分解 first 参数,并拆分part中的第一个parts。这是我们的第一个regExp result段。

    • subs... 是注入的vars中的替代值:

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

  2. ...subs
  3. 循环,并连接正则表达式 string

    1. escape()来自当前stubs[i]的特殊字符(这将破坏最终表达式)。
    2. 添加转义的 stubs[i],并在两者之间添加parts[i]
  4. 模板文字字符串前面加上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工厂类”从较小的片段构建更复杂的表达式。 例如。一个RegExp来解析/验证类似{em> javascript Content-TypeMIME types标头值。语法:media-type = type "/" subtype。 这就是我们想要找到的:

  • */*
  • application/*application/javascriptapplication/ecmascript
  • text/*text/javascripttext/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等),但是没有设置器。

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

本节演示了三种处理标志的方法。

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

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

线程标志像其他替换变量一样。我们需要检查最后一个变量是否为 flag-like gmi,..),并将其与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-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标记类的替代,并且已从subs...数组中删除,该数组现在为空[]reduce()返回一个空字符串'',而new RegExp('', 'i')添加一个空的非捕获组:(?:)

选项B:扩展class RegExp

我们可以扩展RegExp并添加 getter 方法来设置标志。使它们自动返回,以便我们将其链接起来。我们甚至可以添加_“清除标志”方法。

这很好用,但也有效果,每个添加/删除的标志都会产生一个TRegExp的新克隆。如果我们构建“静态”(可缓存)表达式,那么对您来说可能就可以了。

  • A:在循环内的构造函数中添加 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 ━┻━┛


更多阅读内容