Dmitry Parzhitsky Dmitry Parzhitsky - 6 months ago 11
Javascript Question

Weird behavior of `map()`

A.S.: Variable names were changed for simplicity.

My goal is to create new array-like class, every instance of which will have seven numbers, based on whatever quantity of whatever arguments.

So, the code is something like this:

"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)));
}
}


It works fine, so
new SevenNumbers();
returns array of seven numbers, either default (0), or not.

The
debugger
-part is important: it tells us about getting into constructor of a
SevenNumbers
class.

The problem emerges when I try to
map()
gotten object.

Try the following code:

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].


First part works in predicted way, but at the 3rd line we're getting in the constructor again!

The most confusing thing is that the only argument here is the length of
arr
. Why the length?

Although the final result is correct, this weird behavior interferes with my work in practice, since there are additional calculations in between.

What is wrong here? Why doesn't it just take values from ready custom array, why create new one?

Answer

That's because map uses ArraySpeciesCreate to create an array like the original one.

If this didn't happen, at the end you would get a normal Array instead of a SevenNumbers instance.

Therefore, your constructor is called in order to construct the new array which will be filled using the callback function and the items of the original instance.

If you don't want it, you can use Symbol.species to make map call Array instead of SevenNumbers.

class SevenNumbers extends Array {
  constructor(...args) { 
    console.log(args + '');
    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(_ => _);