使用数组对象作为ES6 Map的键

45

我正尝试将我的代码升级到ES6,因为我正在使用Node 4.0,并且迄今为止非常喜欢它的功能。然而,由于新的ES6 Map数据结构在使用Array作为键时与{}的行为不同,因此遇到了问题。我将其用作计数器映射。

我运行了这段代码,想知道如何在Map中使用数组作为键。

"use strict";

var a = new Map();
a.set(['x','y'], 1);
console.log(a.get(['x','y']));

var b = {};
b[['x','y']] = 1;

console.log(b[['x','y']]);

它会打印出以下内容,第一行应该是1而不是undefined

undefined
1

原始的JS map会将键(key)转换为字符串,但我不想在新的ES6 Map中使用同样的字符串转换技巧。

有什么方法可以可靠地将数组作为ES6 Map的键(key)使用?


2
可能是使用元组或对象进行映射的重复问题。 - FluxLemur
1
简而言之:JavaScript中的集合全部将数组键转换为字符串... 因此您只有两种可能性:A. 使用字符串(考虑到JS集合无论如何都会这样做,如果您正在执行元组操作,则这可能不是一个坏方法,即key.split(",")[0] key.split(",")[1]),或B. 将数组拆分并将其元素映射到彼此的子映射中,就像anko的答案+数组键映射一样... - Andrew
5个回答

24

理解ES2015 Map键的比较方式(几乎等同于===运算符)。即使两个数组实例包含相同的值,它们也不会被视为===相等。

尝试一下:

var a = new Map(), key = ['x', 'y'];
a.set(key, 1);
console.log(a.get(key));

由于Map类旨在可用作基类,您可以实现一个子类,并覆盖.get()函数。

(第一句话中的“几乎”是为了反映使用Object.is()进行Map键相等比较这一事实,在日常编码中并不经常出现。它本质上是JavaScript中关于相等性测试的第三个变体。)


2
感谢您提供详细的答案,但覆盖.get()不是我想要做的。 - jimjampez

13

你需要保存一个非基本类型实例的引用,它是你用作键的Array。请注意以下两个示例之间的区别:

"use strict";

var a = new Map();
a.set(['x','y'], 1);
console.log(a.get(['x','y']));
console.log(['x','y'] === ['x','y']);

var b = new Map();
var array = ['x','y'];
b.set(array, 1);
console.log(b.get(array));
console.log(array === array);


12

如果有人想要更基础的方法,可以将键值进行自定义的一对一字符串转换,例如 ${x}_${y}(依赖于特定的键结构)或 JSON.stringify(key) (更为通用)- 这并不是完美解决方案,但在许多情况下可能是最可靠的。

我知道楼主要求其他方法,但我认为作为标题问题的直接答案,这个方法还是需要提到的。


12

我也有这个需求,所以我写了一个遵循 ISC 许可的库:array-keyed-map。你可以在npm上找到它。我认为源码非常清晰,但为了后人着想,以下是它的工作原理:


维护一个Map对象的树形结构。每个树存储:

  • 在内部声明的Symbol键下:该位置上的值(如果有)。Symbol保证独一无二,因此没有用户提供的值可以覆盖此键。

  • 在除了Symbol键以外的所有其他键上:从此树开始所有已设置的下一个树。

例如,在akmap.set(['a', 'b'], true)中,内部树形结构将如下所示:

'a':
  [value]: undefined
  'b':
    [value]: true

接着执行akmap.set(['a'], 'okay')只会更改路径'a'的值:

'a':
  [value]: 'okay'
  'b':
    [value]: true

要获取数组的值,请遍历数组并从树上读取相应的键。如果在任何时候树不存在,则返回undefined。最后,从已到达的树上读取内部声明的[value]符号。

要删除数组的值,请执行相同的操作,但删除[value]符号键下的任何值,并在递归步骤结束后删除任何子树,如果它们的size为0。


为什么使用树?因为当多个数组具有相同的前缀时(这在实际使用中非常典型,例如处理文件路径),它非常高效。


0

我在我的情况下遇到了类似的问题,我想获得一个给定值的矩阵。因此,我的第一种方法是像这样填充地图。

const matrix = new Map()
const initMatrix = () => {
    for (var i = 0; i < 20 ; i++)
        for (var j = 0; j < 20; j++) {
            matrix.set([i,j], Math.random() <= 0.1)
        }
    }
}

matrix.forEach((value, key) => {
    if (value){
        ctx.fillStyle = "rgba(255, 0, 0, 1)";
        ctx.fillRect(key[0] * boxWidth, key[1] * boxHeight,boxWidth, boxHeight);
    }
})
    

一开始一切都很顺利,但是在我多次调用initMatrix函数后,我发现我的矩阵每次调用都会增加400个条目(是bug还是feature?)。所以我四处寻找解决方案,最终找到了这个网站,里面有很多非常好的解决方案。但对我来说并不适用 :(。所以经过更多时间的努力,我找到了一个适合我的解决方案。我只需要添加JSON.stringify和JSON.parse来添加和获取映射的键。这解决了我的问题。希望这能帮助其他人。也许有更好的创建矩阵的解决方案,但这个解决方案对我来说已经足够好了 :)。感谢其他帖子在这里挽救了我的一天 :)

这是我的最终代码:

const matrix = new Map()
const initMatrix = () => {
    for (var i = 0; i < 20 ; i++)
        for (var j = 0; j < 20; j++) {
            matrix.set(JSON.stringify([i,j]), Math.random() <= 0.1)
        }
    }
}

matrix.forEach((value, jsonkey) => {
    if (value){
        const key = JSON.parse(jsonkey)
        ctx.fillStyle = "rgba(255, 0, 0, 1)";
        ctx.fillRect(key[0] * boxWidth, key[1] * boxHeight,boxWidth, boxHeight);
    }
})

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