【问题标题】:Why does Array.prototype.map ignore empty slots in a sparse array whereas Array.prototype.join does not?为什么 Array.prototype.map 会忽略稀疏数组中的空槽,而 Array.prototype.join 不会?
【发布时间】:2021-05-07 16:15:41
【问题描述】:

Array.prototype.map 函数在应用于具有undefined 值的数组时按预期工作:

const array = [undefined, undefined, undefined];
console.log(array.map(x => 'x')); // prints ["x", "x", "x"]

但是,当在具有空槽的稀疏数组上使用 map 时,它不会像前面的示例那样将它们映射到“x”。相反,它返回 undefined 值:

const array = [,,,];
console.log(array.map(x => 'x')); // prints [undefined, undefined, undefined]

即使我们有一个混合了空槽和实际值的数组,也只有后者被映射:

const array = [,'a',,'b',];
console.log(array.map(x => 'x')); // prints [undefined, "x", undefined, "x"]

相比之下,我注意到Array.prototype.join 在空槽上工作:

const array = [,,,,];
console.log(array.join('x')); // prints "xxx"

为什么join 将空槽视为有效元素,而map 则不?

此外,在join documentation 中,他们提到如果元素是undefinednullempty array [],则将其转换为空字符串。他们没有提到空槽,但似乎他们也在将它们转换为空字符串。

这是否是 MDN 文档中的问题?为什么不让join 也像map 一样忽略空槽呢?这似乎是文档中的问题或join 的实现中的问题。

【问题讨论】:

  • 因为它是这样定义的。它通过不通过空槽来处理稀疏数组。与任何数组迭代方法的行为相同:somereducefilter 等。
  • 从您链接的文档中:“map 按顺序为数组中的每个元素调用一次提供的回调函数,并从结果中构造一个新数组。回调仅对索引调用分配了值的数组(包括未定义的)。"
  • 我不知道为什么会很奇怪。如果您有一个稀疏的对象数组并且想要执行objs.map(o => o.id) 之类的操作,则您不需要空值守卫,也不需要更改结果的数量。这似乎完全合乎逻辑。我的参考实际上是规格。您可以查看MDN documentation,它描述了这一点并链接到相关规格。
  • 我明白你们的意思了。 map 文档没有明确提及空槽。实际上,一般来说,它们周围的文档很少。但是,是的,这确实是有道理的。
  • @AlbertoTrindadeTavares 它说:“map 调用提供的回调函数为数组中的每个元素调用一次” - 强调就在链接中。在该段落之后,它明确表示“不需要数组的缺失元素;即:从未设置的索引;已删除的索引;或从未分配过值的索引。”它实际上只是列举了一个空槽的所有可能性。这对我来说似乎很明确。

标签: javascript arrays


【解决方案1】:

join 尝试生成数组的序列化表示。 map 通过一些转换函数生成数组元素的投影。

使用map,可以说:“当您逐步遍历数组时,如果遇到没有属性的索引,请在输出数组中同样取消设置该属性。”对于所有现有属性,输出索引仍将与其输入索引相对应,并且在输入和输出中都会跳过缺失的属性。

使用join 的字符串输出,我们不能真正做到这一点。如果我们加入[,'a',,'b',],a,,b, 的输出是表示这一点的最佳方式。跳过缺失属性的输出(即a,b)将具有极大的误导性,它似乎是一个长度为 2 的数组,其元素位于索引 01

map 不同,它可以生成具有各种存在或不存在属性的数组,join 被困在渲染字符串输出中,它无法在输出中轻松区分缺失和空属性,而不会产生极大的误导性结果。

为了完整起见,这里是函数在输入数组中循环的实际 ECMAScript 指定行为(在每个数组中,k 是循环变量):

Array.prototype.join

重复,而 k len

  • 如果 k > 0,则将 R 设置为 Rsep 的字符串连接。李>
  • 元素成为?获取(O, !ToString(k))。
  • 如果 elementundefinednull,则让 next 为空字符串;否则,让 next 成为 ? ToString(元素)。
  • R 设置为 Rnext 的字符串连接。
  • k 增加 1。

Array.prototype.map

重复,而 k len

  • Pk成为! ToString(k).
  • kPresent 成为? HasProperty(O, Pk).
  • 如果 kPresenttrue,则
    • kValue 为 ?获取(OPk)。
    • mappedValue 为 ?调用(callbackfn, T, « kValue, k, O »)。
    • 执行? CreateDataPropertyOrThrow(A, Pk, mappedValue).
  • k 增加 1。

即使您不知道如何阅读所有这些内容,也很容易看到map 在第二个循环步骤中包含HasProperty 检查。 join 明确表示“如果元素是 undefinednull,则让 next 为空字符串。” Get(O, ! ToString(k)) 是一种常见的属性查找,对于普通对象,当属性不存在时会生成 undefined,因此适用“如果元素为 undefined”的情况。

值得注意的是,MDN 文档简化了其信息,以便专注于最常见的情况,而不是坚持严格的完整性。 (我会说稀疏数组是一种不常见的情况。)特别是,他们说空数组将序列化为空字符串,这是真的。对于具有返回空字符串的toString 函数的任何值来说,这通常是正确的:

["foo", { toString: a=>""}, "bar"].join()

这将产生输出foo,,bar

【讨论】:

  • 感谢您非常详细的回答!我想知道如果元素是空槽,join 是否应该跳过Set R to the string-concatenation of R and next,但当前的实现是有意义的,同时考虑到你之前写的内容。在将此答案标记为已接受之前会稍长一点(以防其他人尝试回答),但很可能会接受您的:)
【解决方案2】:

如果您在这里不是为了摘录教程,而是为了让数组方法以所需方式运行,请考虑以下几点:


const array = [,,,];
console.log([...array].map(x => 'x'));

...如果您需要初始大小的结果数组,或者

const array = [,'a',,'b',]
console.log([...array].filter(Boolean).map(x => x+'x'));

...如果您需要跳过空槽

【讨论】:

  • 问题是“为什么会这样工作”你的答案没有涵盖这一点,而是回答了一个不同的问题。
  • @VLAZ :我个人认为解决问题的解决方案比参考文档第一章中的一个简单句子的冗长论述更有价值
  • 我知道这里没有问题。 .map 完全按其应有的方式工作,并且与所有数组迭代方法完全相同,因为它跳过了丢失的插槽。所以,它按预期工作。如果有人遇到这个问题,那么他们可以问这个问题,但问题不是“我想映射丢失的插槽”。是的,我同意这个问题没有什么意义,因为它链接到描述行为的文档并询问为什么会发生这种情况,但这只是意味着没有太多要真正回答的问题:“为什么 X 做 Y 给定文档 "
【解决方案3】:

这就是 JS 处理稀疏数组的方式。考虑:

> let a = [,,,,,]
> a
[ <5 empty items> ]
> let b = [undefined,undefined,undefined,undefined,undefined]
> b
[ undefined, undefined, undefined, undefined, undefined ]
> let c = [,,'x',,]
> c
[ <2 empty items>, 'x', <1 empty item> ]

空项目实际上并不占用任何空间(除了一些开销)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-15
    • 2015-01-13
    • 1970-01-01
    • 2016-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多