11

I was trying to use new Array() constructor with map in order to create a one-line code that creates a list of elements. Something like this :

let arr = new Array(12).map( (el, i) => {
  console.log('This is never called');
  return i + 1;
});

Reading docs, the behaviour makes sense.

Basically docs say that callback of map will be executed even for declared undefined values in array, but not for example when creating empty Arrays like the code before.

So this should work :

var arr = new Array(12);

for(let i = 0; i < arr.length ; i++){
  arr[i] = undefined;
}

let list = arr.map( (e, i) => {
  console.log(i + 1);
  return i + 1;
});

So, We also can do something like this :

let newArray = (length) => {
  let myArray = new Array(length);
  for(let i = 0; i < length; i++) myArray[i] = undefined;
  return myArray;
};

console.log( newArray(12).map( (el, i) => i + 1 ) );

So my question. Is there a better/pretty way to do it with map function ?

Thanks in advance!

Jose Hermosilla Rodrigo
  • 3,513
  • 6
  • 22
  • 38

6 Answers6

20

You can use Array#from to create an array having passed length.

Array.from({length: 12}, (e, i) => i + 1);
Tushar
  • 85,780
  • 21
  • 159
  • 179
  • Pretty cool! I'll give a read to Array.from docs! Thanks for this useful answer! Answer will be marked in 9 minutes! =) – Jose Hermosilla Rodrigo May 27 '16 at 15:51
  • I'm not the voter, but I would point out that `Array.from` accepts a callback that behaves like `.map()`. If you use that, then you're not creating an unnecessary intermediate Array. –  May 27 '16 at 16:02
  • I don't know. I upvoted you. I think is pretty cool, so i'm gonna mark this answer because is cleaner and works for me. And was the first. =) – Jose Hermosilla Rodrigo May 27 '16 at 16:03
  • @squint You're right, but that's my fault because of lack of knowledge, because I say that I want to use map, so Array.from({length: 12}, (el, i) => i +1); should work as intended in the question. – Jose Hermosilla Rodrigo May 27 '16 at 16:05
  • 1
    @JoseHermosillaRodrigo: Yes, you did ask for `.map`. I'm just pointing it out so that people don't unnecessarily use this in practice. Nothing wrong with this answer from what I can see, so +1. –  May 27 '16 at 16:09
  • 1
    @squint and Tushar. I appreciate your help! =) – Jose Hermosilla Rodrigo May 27 '16 at 16:12
11

If I am not mistaken, you explicitly said that you wanted an answer with new Array constructor.

Here is how to do it. One line, beautiful ES6:

let me = [...new Array(5)].map((x,i)=>i)
// [0,1,2,3,4]

Explanation:

  1. new Array(5) generates this: [ , , , , ]​​​​​
  2. [...new Array(5)] is a shorthand for Array.from(new Array(5)), and generates this: ​​​​​[ undefined, undefined, undefined, undefined, undefined ]​​​​​, that is an array of undefined objects. Yes, undefined is a value (a primitive one :) ).
  3. Now we can iterate over the values of our brand new array :D

PS: you can even skip the new keyword and do:

[...Array(5)].map((x,i)=>i)

Extra: Tersest expression

[...Array(5).keys()] // [ 0, 1, 2, 3, 4 ]
Soldeplata Saketos
  • 3,212
  • 1
  • 25
  • 38
6

Section 22.1.3.15 of the spec is getting you, with the last line of NOTE 1:

callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.

This behavior is shared by filter, forEach, and the other functional methods.

The array constructor you are calling, Array(len) from section 22.1.1.2, will set the length of the array but not initialize the elements. I'm not sure where the spec defines what a missing element is, but the array produced by new Array(len) should fit that and so none of the elements will be touched by map.

ssube
  • 47,010
  • 7
  • 103
  • 140
  • 2
    The array created by `new Array(n)` contains **holes** i.e. `[undefined × n]` and thus cannot be iterate over by `map`, `filter`, `forEach`... – Tushar May 27 '16 at 15:53
  • 1
    @Tushar the spec considers the array to have missing elements, not "holes." There are subtle differences between a sparse array and one with unset elements like this. – ssube May 27 '16 at 16:00
  • 1
    It doesn't initialize the array with `undefined` values. Simply put the object doesn't have any properties from 0 to whatever length was specified. – MinusFour May 27 '16 at 16:01
3

I like this approach because it still works in ES5:

let arr = Array.apply(null, Array(12))
    .map(function (x, i) {
        return i
    });

// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Since New Array returns an array that contains "holes", we use apply because apply treats holes as if they were undefined. This spreads undefined across the entire array which makes it mappable.

KevBot
  • 17,900
  • 5
  • 50
  • 68
  • Sorry for my lack of knowledge, but could you please explain me why it works? What is Array.apply() doing? – Jose Hermosilla Rodrigo May 27 '16 at 15:58
  • 1
    @JoseHermosillaRodrigo, I've added some clarification to my answer. Also, here is some documentation on [`apply`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) – KevBot May 27 '16 at 16:04
3

You could use Array.apply with an object with the length in it.

var array = Array.apply(null, { length: 12 }).map((_, i) => i + 1);
console.log(array);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

thanks for answer of @ssube

let arr = new Array(12).fill(0).map( (el, i) => {
  console.log(`This should called ${i} times`);
  return i + 1;
});
// output;
// This should called 0 times
// This should called 1 times
// This should called 2 times
// This should called 3 times
// This should called 4 times
// This should called 5 times
// This should called 6 times
// This should called 7 times
// This should called 8 times
// This should called 9 times
// This should called 10 times
// This should called 11 times
kin
  • 139
  • 2
  • 6