为什么 Array.prototype.map 会忽略稀疏数组中的空槽,而 Array.prototype.join 不会?

Alb*_*res 6 javascript arrays

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

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

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

const array = [,,,];
console.log(array.map(x => 'x')); // prints [undefined, undefined, undefined]
Run Code Online (Sandbox Code Playgroud)

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

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

相反,我注意到Array.prototype.join空槽上的工作:

const array = [,,,,];
console.log(array.join('x')); // prints "xxx"
Run Code Online (Sandbox Code Playgroud)

为什么join将空槽视为有效元素,但map事实并非如此?

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

那么这是 MDN 文档中的问题吗?为什么不以join同样的方式忽略空槽map呢?这似乎是文档或实现中的问题join

Yev*_*kov 6

如果您来这里不是为了教程摘录,而是为了让数组方法以所需方式运行的实用方法,请考虑以下事项:


const array = [,,,];
console.log([...array].map(x => 'x'));
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 问题是“为什么会这样工作”,您的答案没有涵盖这一点,而是回答了另一个问题。 (3认同)
  • @VLAZ:就我个人而言,我认为您可以用来解决问题的解决方案比围绕参考文档第一章中的简单句子进行冗长的论述更有价值 (3认同)

aps*_*ers 4

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

\n

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

\n

对于join字符串输出,我们无法真正做到这一点。如果我们加入[,\'a\',,\'b\',], 的输出,a,,b,是表示这一点的最佳方式。跳过缺失属性的输出(即,a,b)将具有极大的误导性,它看起来是一个长度为 2 的数组,其元素位于索引0和 处1

\n

与 不同的是map,它可以生成具有各种存在或不存在属性的数组,但join它在渲染字符串输出时遇到了困难,它无法轻松地区分输出中缺失的属性与空的属性,而不会产生巨大的误导性结果。

\n

为了完整起见,以下是 ECMAScript 指定的实际行为,其中函数循环输入数组(每个数组中k都有循环变量):

\n

Array.prototype.join

\n
\n

重复,当k < len时

\n
    \n
  • 如果k > 0,则将R设置为Rsep的字符串连接。
  • \n
  • 元素为 ? Get( O ,!ToString( k ))。
  • \n
  • 如果element未定义或null,则让next空字符串;否则,接下来是?ToString(元素).
  • \n
  • R设置为Rnext的字符串连接。
  • \n
  • 将k增加1。
  • \n
\n
\n

数组.prototype.map

\n
\n

重复,当k < len时

\n
    \n
  • 就让Pk吧!ToString( k ).
  • \n
  • kPresent为 ? HasProperty( O , Pk ).
  • \n
  • 如果kPresenttrue,则\n
      \n
    • kValue为 ? 获取(OPk)。
    • \n
    • mappedValue为?调用(callbackfnT,\xc2\xab kValuekO \xc2\xbb)。
    • \n
    • 履行 ?CreateDataPropertyOrThrow(APkmappedValue)。
    • \n
    \n
  • \n
  • 将k增加1。
  • \n
\n
\n

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

\n

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

\n
["foo", { toString: a=>""}, "bar"].join()\n
Run Code Online (Sandbox Code Playgroud)\n

这将产生输出foo,,bar

\n