`map()` 的奇怪行为

3

A.S.: 因简化起见,变量名称已更改。

我的目标是创建一个新的类似数组的类,每个实例都将基于任何数量的任何参数具有七个数字。

因此,代码类似于以下内容:

"use strict";
class SevenNumbers extends Array {
    constructor(...args) { debugger; // !
        // Cut the rest; fill the gaps; convert: first to object, then to primitive
        super(...Object.assign([0, 0, 0, 0, 0, 0, 0], args.slice(0, 7)).map(arg => +new Number(arg)));
    }
}

它运行良好,因此new SevenNumbers();返回一个由七个数字组成的数组,可以是默认值(0),也可以不是。
debugger部分很重要:它告诉我们进入了SevenNumbers类的构造函数。
当我尝试对获取的对象进行map()时,问题就出现了。 请尝试以下代码:
var arr = new SevenNumbers(8, 7, 6, 5, 4, 3, 2, 1);
// SevenNumbers [8, 7, 6, 5, 4, 3, 2] (without the last).
arr.map(_ => _);
// Array [8, 7, 6, 5, 4, 3, 2].

第一部分按照预期工作,但在第三行中我们再次进入构造函数!最令人困惑的是,这里唯一的参数是arr的长度。为什么是长度?

虽然最终结果是正确的,但这种奇怪的行为干扰了我的实际工作,因为之间还有额外的计算。

这里出了什么问题?为什么不直接从现成的自定义数组中获取值,而要创建一个新的数组?


1
也许 .map 函数不仅仅是创建一个数组,而是创建其调用者的实例呢?(特别是当该调用者继承自数组时)。 - coyotte508
@coyotte508:那么为什么它要将length属性作为参数呢?这个问题的这一部分是最奇怪的。 - Parzh from Ukraine
1
@DmitryParzhitsky:因为new Array(length)是标准构造函数。 - Bergi
1
实际上这是预期的。"使用参数originalArray和length的抽象操作ArraySpeciesCreate用于指定使用派生自originalArray的构造函数创建新的Array对象。它执行以下步骤:" - coyotte508
@DmitryParzhitsky—好的,我只是想知道是否有什么关于 +new Number(5) 的微妙之处我错过了。 :-) - RobG
显示剩余8条评论
1个回答

4

这是因为map使用ArraySpeciesCreate来创建一个类似于原始数组的数组。

如果不这样做,最终你将得到一个普通的Array而不是SevenNumbers实例。

因此,会调用你的构造函数来构造新的数组,该数组将使用回调函数和原始实例的项进行填充。

如果不想这样做,可以使用Symbol.species使map调用Array而不是SevenNumbers

class SevenNumbers extends Array {
  constructor(...args) { 
    console.log(args.toString());
    super(...Object.assign([0, 0, 0, 0, 0, 0, 0], args.slice(0, 7)).map(arg => +new Number(arg)));
  }
  static get [Symbol.species]() { return Array; }
}
new SevenNumbers(8, 7, 6, 5, 4, 3, 2, 1).map(_ => _);


3
值得补充的是,如果支持Symbol.species,则可以使用它来改变这种行为。 - Retsam
@Retsam 是的,我本来想提到它,但开始无法使其工作。我以为它是作为自身数据属性在某个步骤中存储的,但实际上它是从“Array”继承的getter方法。 - Oriol

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