如何将RegExp字面量用作对象键?

41

我想使用创建一个包含正则表达式作为键值的对象。我尝试使用以下语法:

var kv = {
    /key/g : "value"
};

但是根据JavaScript lint的检测,它失败了:

SyntaxError: invalid property id

我该如何修复它?

更新

背景:我想这样做的原因是为了实现一个解决HTTP API结果中错误Unicode的变通方法。我知道这样做很狡猾,但由于我无法控制API服务器代码,我认为这是我所能做的最好的。

目前,我通过拥有一个键数组和一个值数组来实现映射:

function fixUnicode(text) {

    var result = text;
    var keys = [];
    var values = [];
    keys.push(/é/g); values.push("é");
    keys.push(/è/g); values.push("è");
    keys.push(/ê/g); values.push("ê");
    keys.push(/ë/g); values.push("ë");
    keys.push(/à/g); values.push("à");
    keys.push(/ä/g); values.push("ä");
    keys.push(/â/g); values.push("â");
    keys.push(/ù/g); values.push("ù");
    keys.push(/û/g); values.push("û");
    keys.push(/ü/g); values.push("ü");
    keys.push(/ô/g); values.push("ô");
    keys.push(/ö/g); values.push("ö");
    keys.push(/î/g); values.push("î");
    keys.push(/ï/g); values.push("ï");
    keys.push(/ç/g); values.push("ç");

    for (var i = 0; i < keys.length; ++i) {
        result = result.replace(keys[i], values[i]);
    }
    return result;
}

但是我想实现使用JavaScript对象将键映射为值:

function fixUnicode(text) {

    var result = text;
    var keys = {
        /&Atilde;&copy;/g : "&eacute;",
        /&Atilde;&uml;/g :  "&egrave;"
        // etc...
    };

    for (var key in keys) {
        result = result.replace(key, keys[key]);
    }
    return result;
}

这可能吗?我知道你可以使用正则表达式字符串创建一个值,但我从未听说过使用正则表达式作为键。 - stslavik
有什么用例,为什么不使用 var kv = [{rx: 'key1', modifier: 'g', value:'value1'}, {rx: 'key2', flags: 'g', value:'value2'}],然后循环遍历数组并使用 var rx = new RegExp(kv[0].rx, kv[0].modifier); 构建正则表达式呢? - Shef
5个回答

56

这是可以实现的,但不能使用对象字面量语法。你需要按照下面的方式进行:

var kv = {};
kv[/key/g] = "value";
console.log(kv[/key/g]); // "value"


编辑:这可能需要一些解释。 正如xanatos在下面评论的那样,这里真正发生的事情是将键/key/g(在本例中)转换为字符串toString()来创建键。 这很重要,因为它对键的唯一性产生影响。请考虑以下内容:

var x = {},
    reg = /foo/;

x[reg] = 'bar';
console.log(x[reg]); // "bar"
console.log(x[reg.toString()]); // "bar"
console.log(x['/foo/']); // "bar'

简而言之,我有点害怕问你为什么需要这样做,但是假设你有你的理由,请小心并确保你理解正在发生的事情 :)


编辑2:根据你更新后的问题,你应该能够实现接近你想要的东西。只要你用引号包裹正则表达式,就可以使用对象文字语法。不幸的是,这意味着你将不得不手动重构一个 RegExp 对象出来。例如:

var result = "abcdef",
    replacements = {
        "/a/g": "FOO",
        "/d/i": "BAR"
    };

for (var key in replacements) {
    var parts = key.split('/');
    result = result.replace(new RegExp(parts[1], parts[2]), replacements[key]);
}

console.log(result); //FOObcBARef


第三次编辑:因为为什么不呢。我一直在着迷于让你的对象字面量语法起作用,以至于我没有考虑到你永远不需要根据模式本身查找替换(即,根本不需要对象键)。这里是一种更有效的方法,使用数组而不需要RegExp重构:

var result = "abcdef",
    replacements = [
        [/a/g, "FOO"],
        [/d/i, "BAR"]
    ];

for (var i = 0, len = replacements.length; i < len; i++) {
    var replacement = replacements[i];
    result = result.replace(replacement[0], replacement[1]);
}

console.log(result); //FOObcBARef


编辑4: 因为我很无聊,我喜欢这个问题。这是忍者版:

var result = "abcdef",
    replacements = [
        [/a/g, "FOO"],
        [/d/i, "BAR"]
    ], r;

while ((r = replacements.shift()) && (result = String.prototype.replace.apply(result, r))) {}

console.log(result); //FOObcBARef

4
最终将使rx成为一个字符串。这就像执行kv[(/key/g).toString()] = "value"一样。 - xanatos
好观点(+1)- 我已经扩展了我的答案,包括解释。 - jmar777
对使用连续数组的深入洞察很有帮助。你曾经质疑它的使用场景,但是我在编写语法分析器时发现了它的巨大优势——可以指定匹配规则。数组中的第一个元素是用来匹配的正则表达式,而第二个元素则是该模式匹配的标记值。 - jhewlett
如果替换内容不是字面量,并且应包括匹配组,怎么办? - ccpizza
@ccpizza,你能展示一下你所说的例子吗?上面任何一个解决方案都应该仍然适用于常规替换语法(例如,“$1,$2,$3”)。 - jmar777

16

我认为这个问题应该得到更新的答案。自ES6起,创建了一种新类型(标准内置对象)Map,以覆盖类似于此类的情况。

Map与Object非常相似,除了它允许任何类型作为键。

然后可以使用Map.prototype.forEach() 遍历每个键/值对。

在您的情况下,现在可以使用以下函数:

function fixUnicode(text) {

    var result = text;
    var replaceMap = new Map();
    replaceMap.set(/&Atilde;&copy;/g, "&eacute;");
    replaceMap.set(/&Atilde;&uml;/g, "&egrave;");
    replaceMap.set(/&Atilde;&ordf;/g, "&ecirc;");
    replaceMap.set(/&Atilde;&laquo;/g, "&euml;");
    replaceMap.set(/&Atilde;&nbsp;/g, "&agrave;");
    replaceMap.set(/&Atilde;&curren;/g, "&auml;");
    replaceMap.set(/&Atilde;&cent;/g, "&acirc;");
    replaceMap.set(/&Atilde;&sup1;/g, "&ugrave;");
    replaceMap.set(/&Atilde;&raquo;/g, "&ucirc;");
    replaceMap.set(/&Atilde;&frac14;/g, "&uuml;");
    replaceMap.set(/&Atilde;&acute;/g, "&ocirc;");
    replaceMap.set(/&Atilde;&para;/g, "&ouml;");
    replaceMap.set(/&Atilde;&reg;/g, "&icirc;");
    replaceMap.set(/&Atilde;&macr;/g, "&iuml;");
    replaceMap.set(/&Atilde;&sect;/g, "&ccedil;");

    replaceMap.forEach(function (newString, old) {
      result = result.replace(old, newString);
    });

    return result;
}

你可以在MDN上阅读更多关于地图的信息。


很棒!非常有用,可以将字符串URL转换为字符编码。 - Standaa - Remember Monica

3

对象键不能是RegExp对象。您必须使用字符串或有效的ID。话虽如此,您可以这样做:

var kv = {
    "/key/g": "value"
};

我很好奇。你为什么想要这样做呢?

编辑:我部分地错误了。RegExp对象可以用作键,但不能使用对象字面量语法。请参见jmar777的答案。


2
正则表达式不能被使用,任何不是字符串的东西在成为哈希映射中的键之前都会被转换为字符串。因此,真正发生的是您所建议的(使用正则表达式的字符串版本)。 var o = {}; o[/key/g] = "value"; for (var prop in o) {console.log(prop instance of RegExp)} // yields false. 话虽如此,RegExp 的 toString() 方法可能已经足够好了。 - Ruan Mendes

0

虽然这个话题已经老旧了,但是增加另一个选项作为参考可能会有用。

虽然正则表达式不能作为对象属性,但是可以作为值。

选项

  • 将正则表达式字符串作为对象属性,然后使用new RegExp()进行转换 (如前所述)
    注意事项:需要运行额外的构造函数并且需要双重转义特殊字符,例如\\s+
  • 在数组中使用正则表达式和替换对(如前所述)
  • 交换对象中的属性和值,即将替换属性和正则表达式值互换

let str = 'Change this key and THIS KEY.';
const obj = {
  'value': /key/gi,
  'that': /this/gi
};
Object.entries(obj).forEach(([key, value]) => str = str.replace(value, key));
console.log(str);
// Change that value and that value.

注意:

  • 箭头函数是在 ECMAScript 2015(也称为 ES6)中添加的
  • Object.entries() 是在 ECMAScript 2017(也称为 ES8)中添加的
  • Object.keys()for 循环可用于旧版浏览器

0

在创建对象时,您还可以使用表达式作为键,只需将其包装在[]中:

var kv = {
    [/key/g] : "value"
};

然而,关键字仍然是一个字符串。不是正则表达式对象/文字。 - Knight Yoshi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接