6

I'm just learning how to use JS higher-order functions (map, forEach, reduce, etc), and have stumbled into confusion. I'm trying to write a simple 'range' function, but can't seem to populate my output array. This is the goal:

range(1, 4) // [1, 2, 3, 4]

I'm getting this:

[undefined × 4]

Here is my code:

 function range(num1, num2) {
      var rangeArr = new Array((num2 + 1) - num1);
      return rangeArr.map(function(e, i, arr) {return arr[i] = num1 + i});
    }

What am I missing here? As far as I can tell the problem appears to have something to do with the way I'm utilizing 'new Array', but beyond that I'm lost.

Oh, and here's the part that really confuses me. This works fine:

function bleck() {
    var blah = [1, 2, 3, 4];
    var x = 'wtf';
    return blah.map(function(e, i, arr) {return arr[i] = x})
}

["wtf", "wtf", "wtf", "wtf"]

Thanks!!

i_made_that
  • 941
  • 1
  • 8
  • 19
  • Don't think you need "(num2 + 1) - num1)", since javascript uses dynamic arrays. – Serdnad Apr 24 '14 at 23:23
  • @Serdnad It's not necessary, but it does construct an empty array of that length: `Array(4) = [undefined x 4]` – cincodenada Apr 24 '14 at 23:25
  • 1
    Note about using `.map()` that isn't related to your problem: in your callback there's no need to have `return arr[i] = num1 + i`, you can just say `return num1 + i;`. There's no point setting the values in `arr` - which is the same array as `rangeArr` - because `map` constructs a new array and that is what you are returning from your `range()` function. – nnnnnn Apr 24 '14 at 23:36
  • @Serdnad Do you mean that I could simply have declared an empty array? – i_made_that Apr 24 '14 at 23:47
  • @i_made_that Well, you could have. Javascript uses dynamic arrays, so that it'll grow if need be. Sure, you sacrifice a tad of performance, but it's more convenient sometimes, if you're not sure how large an array you need, and don't want to start with a massive array to compensate. As cincodenada clarified though, not necessary. – Serdnad Apr 26 '14 at 02:34
  • closely related: [JavaScript `new Array(n)` and `Array.prototype.map` weirdness](http://stackoverflow.com/q/5501581/1048572) – Bergi Aug 25 '15 at 22:45

4 Answers4

4

The forEach method iterates over the indices of the array. Interestingly enough, when you create a new array via new Array(n), it contains no indices at all. Instead, it just sets its .length property.

> var a = new Array(3);
> console.info(a)
[]
> console.info([undefined, undefined, undefined])
[undefined, undefined, undefined]

MDN describes forEach, and specifically states:

forEach executes the provided callback once for each element of the array with an assigned value. It is not invoked for indexes which have been deleted or elided.

Here's a neat technique to get an array with empty, but existing, indices.

var a = Array.apply(null, Array(3));

This works because .apply "expands" the elided elements into proper arguments, and the results ends up being something like Array(undefined, undefined, undefined).

voithos
  • 68,482
  • 12
  • 101
  • 116
  • Very cool, worked like a charm!!! Will accept your answer in t-minus 4 mins and counting ;) – i_made_that Apr 24 '14 at 23:30
  • Out of curiosity, any idea why would one want an array with no indices? Doesn't that defeat the purpose? – i_made_that Apr 24 '14 at 23:49
  • @i_made_that: I can't really think of a use case for it. It seems to me to be just another backwards-compatible barnacle for JavaScript's standard API. :] – voithos Apr 24 '14 at 23:58
1

The array is defined with 4 entires each of which is undefined.

Map will not iterate over undefined entires, it skips them.

callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes that are undefined, those which have been deleted or which have never been assigned values.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

user430788
  • 2,143
  • 2
  • 17
  • 17
1

When you create a new Array(x) it is creating what is called a sparse array, which might behave a bit differently, as you can see, some browsers will say [undefined x 20,"foo", undefined x 5] if you just set one value, and I believe it doesn't iterate over those values.

Cristi Mihai
  • 2,505
  • 1
  • 19
  • 20
  • Not sure I quite follow. Do you mean if I added this underneath the `var rangeArr` declaration: `rangeArr[0] = num1`? – i_made_that Apr 24 '14 at 23:34
0

The problem is that map doesn't iterate undefined entries (*).

I suggest using a for loop instead:

var rangeArr = new Array((num2 + 1) - num1);
for(var i=0; i<=num2-num1; ++i)
    rangeArr[i] = num1 + i;
return rangeArr;

(*) With undefined entries I mean rangeArr.hasOwnProperty(i) === false, not to be confused with rangeArr[i] === void 0.

Oriol
  • 274,082
  • 63
  • 437
  • 513
  • [undefined, undefined, undefined].map() will actually iterate – Cristi Mihai Apr 24 '14 at 23:24
  • @CristiMihai I meant this type of undefined: `[,,,].map()` – Oriol Apr 24 '14 at 23:27
  • I understand that `.map()` isn't supposed to iterate over indexes that have never been assigned values, but then why does the OP's code return an array with four undefined elements in it and a `.length` of 4? – nnnnnn Apr 24 '14 at 23:34
  • @nnnnnn Because the returned array is created using `new Array(len)` (see [spec](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.19)), where `len` is the length of the initial array. – Oriol Apr 24 '14 at 23:38
  • Ah. So `.map()` will always return an array of the same length as the array it was called on, even though it may not call the callback for some elements? – nnnnnn Apr 24 '14 at 23:41