为什么我不能在 Set 对象上调用 Array.prototype.map 方法?

5

我希望使用ES6的Set从一些字符串中获取唯一字符。 假设我们有一个字符串var str = 'abcdeaabc';,我们可以从这个字符串创建一组字符:

var str = 'abcdeaadbc';
var chars = new Set(str);

现在我们有一组独特的字符:abcd。 我很惊讶char集合有forEach方法,但没有map方法。 Set是一个可迭代对象,为什么我们不能使用map函数来迭代Set? 我试图将chars集合和chars.values() SetIterator传递给Array.prototype.map这样做:
Array.prototype.map.call(chars, function(element) {...});
Array.prototype.map.call(chars.values(), function(element) {...});

但是每次尝试都失败了。
例如,假设我们想从字符串中获取唯一的符号,并返回一个带有下划线前缀的数组。例如:['_a', '_b', '_c', '_d'] 以下是我的两个解决方案:
// First:
var result = Array.from(chars).map(function(c) {
    return '_' + c;
});

// Second:
var result = [];
chars.forEach(function(c) {
    this.push('_' + c);
}, result);

但是有没有一种方法可以在Set上下文中调用Array.prototypemap函数呢?如果没有,为什么呢?


你尝试过使用Set.entries吗?实际上,再想一想,我认为那行不通... - Mark C.
2
https://dev59.com/tJTfa4cB1Zd3GeqPLAlK#35374005。还有另一种方式:`let result = [...chars].map(c => `_${c}`);` - timolawl
3个回答

6

Array.prototype.map适用于具有整数索引和length属性的类数组类型。 SetMap是通用的可迭代类型,但它们不是类数组,因此Array.prototype方法无法在它们上面使用。例如:

var s = new Set([1, 2, 3]);
s.length === undefined;
s[0] === undefined

主要方法是类似于您的解决方案的Array.from,但需要注意的一点是Array.frommapFn作为第二个参数,因此您可以这样做。
let results = Array.from(chars, c => `_${c}`);

以下是将可迭代对象转换为数组的简洁明了的方法。


集合条目不可通过索引访问,但顺序是有保证的...我无法克服这个:) - maioman
MDN表示Set是可迭代的。而Array.prototype.map的规范说明map接收可迭代对象:MDN: map。那么为什么我不能将set作为参数传递给map呢? - Yuriy Yakym
@YuriyYakym 我认为这是因为 Set 实例不能像数组一样使用数字索引进行索引,而这正是像 .map().filter().forEach() 等数组函数所依赖的。 - Pointy
看一下同一页上的Polyfill,它是规范的逐步实现,你会发现它需要一个length属性,并且值可以通过索引进行访问。 - Heretic Monkey
@YuriyYakym 那个链接是关于 Map 的,不是 Array.prototype.map - loganfsmyth
@loganfsmyth 哦,对不起,我没有注意到。现在变得更清晰了。 - Yuriy Yakym

1
如果你很绝望,可以这样做。

var str = 'abcdeaadbc';
var chars = new Set(str);
document.write("<pre>" + JSON.stringify([...chars].map(c => c+"up")) + "</pre>")


扩展运算符然后映射会无缘无故地创建一个临时数组。你可以使用 Array.from 同时创建一个数组并进行映射。 - loganfsmyth
1
@loganfsmyth: Array.from() 方法可以从类似数组或者可迭代对象创建一个新的数组实例。(这是在MDN中对Array.from()方法第一行的描述) https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from 如果你想要在后续使用函数式编程或将唯一元素作为参数数组传递给函数,那么这个方法非常实用。这样做无需创建冗余的数组。 - Redu
我的观点是 [...chars].map(fn)Array.from(chars).map(fn) 都会创建一个数组,然后调用 .map 返回第二个数组,而你可以使用 Array.from(chars, fn) 来避免创建第一个临时数组。 - loganfsmyth

1
因为即使map足够通用以适用于任意类似数组的对象,但并非所有可迭代对象都是类似数组的。 map只获取length属性,然后从0length-1迭代属性。这在集合中无法工作,因为它们具有size而不是length,并且它们不提供对其元素的随机访问。
如果您真的想使用map,则需要将集合转换为数组,然后将结果转换回集合。
var newSet = new Set(Array.from(set).map(myFunc));

但这样做没有意义,最好只使用集合操作:

var newSet = new Set();
for(let val of set) newSet.add(myFunc(val));

或者可能是一个自调用的生成器函数:

var newSet = new Set(function*() {
  for(let val of set) yield myFunc(val);
}());

Array.from 的结果上进行映射将会无缘无故地创建一个临时数组。你可以将回调函数作为第二个参数传递。 - loganfsmyth

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