35

This is just out of curiosity, but do any of you have an idea why this code won't work?

[1, 2, 3, 4, 5].forEach(console.log);

// Prints 'Uncaught TypeError: Illegal invocation' in Chrome

On the other hand, this seems to work fine:

[1, 2, 3, 4, 5].forEach(function(n) { console.log(n) });

So... ?

James Ko
  • 32,215
  • 30
  • 128
  • 239
Pablo Fernandez
  • 103,170
  • 56
  • 192
  • 232
  • Indeed. Will answer it myself, I'm such an idiot – Pablo Fernandez Mar 09 '12 at 18:35
  • Can it just be deleted? It's not really that helpful =/ – Dagg Nabbit Mar 09 '12 at 18:38
  • @PabloFernandez I meant the question, but I guess someone liked it enough to upvote so maybe it was useful to someone after all... It's never clear to me if upvotes are from people finding the question genuinely useful, or just from answerers trying to game the system. – Dagg Nabbit Mar 09 '12 at 18:40
  • @GGG Fine with me, but you think that's really necessary? it got 2 upvotes... perhaps we can close it as a duplicate of a question that talks about `bind` in js? – Pablo Fernandez Mar 09 '12 at 18:42
  • It would probably be best to close as a dupe of a question about how `this` is resolved... there are other ways than `bind` to work around it. – Dagg Nabbit Mar 09 '12 at 18:44
  • Why close it? This is a really interesting brain teaser. In fact, I'm still trying to wrap my head around it. – Brandon Boone Mar 09 '12 at 18:49
  • Compare the output of these in chrome's console: `[1,2,3,4,5].forEach(console.log.bind(console));` ... `[1,2,3,4,5].forEach(function(x){console.log(x)});` – Dagg Nabbit Mar 09 '12 at 18:49
  • @BrandonBoone yeah, maybe I'm wrong. It might be an unusual enough case that it's hard to tell what's going on at first. – Dagg Nabbit Mar 09 '12 at 18:51
  • yep, this is a good question. @PabloFernandez - I am guessing you got inspiration for this question after reading "Async Javascript" book by Trevor Burnham - page 13 - precisely the same example code :) – arcseldon Dec 15 '15 at 12:21

4 Answers4

31

It's worth pointing out that there is a difference in behavior in the implementation of console.log. Under node v0.10.19 you do not get an error; you simply see this:

> [1,2,3,4,5].forEach(console.log);
1 0 [ 1, 2, 3, 4, 5 ]
2 1 [ 1, 2, 3, 4, 5 ]
3 2 [ 1, 2, 3, 4, 5 ]
4 3 [ 1, 2, 3, 4, 5 ]
5 4 [ 1, 2, 3, 4, 5 ]

This is because the callback to forEach is a three-parameter function taking the value, the index, and the array itself. The function console.log sees those three parameters and dutifully logs them.

Under the Chrome browser console, however, you get

> [1,2,3,4,5].forEach(console.log);
TypeError: Illegal invocation

and in this case, bind will work:

 > [1,2,3,4,5].forEach(console.log.bind(console));
 1 0 [ 1, 2, 3, 4, 5 ]
 2 1 [ 1, 2, 3, 4, 5 ]
 3 2 [ 1, 2, 3, 4, 5 ]
 4 3 [ 1, 2, 3, 4, 5 ]
 5 4 [ 1, 2, 3, 4, 5 ]

but there is an alternative way: note that the second parameter to forEach takes the value of this to use in the callback:

> [1,2,3,4,5].forEach(console.log, console)
1 0 [ 1, 2, 3, 4, 5 ]
2 1 [ 1, 2, 3, 4, 5 ]
3 2 [ 1, 2, 3, 4, 5 ]
4 3 [ 1, 2, 3, 4, 5 ]
5 4 [ 1, 2, 3, 4, 5 ]

which works in the Chrome console and node for me. Of course, I'm sure what you want is just the values, so I'm afraid the best solution is, indeed:

> [1,2,3,4,5].forEach(function (e) {console.log(e)});
1
2
3
4
5

Whether node's behavior is a bug, or it simply takes advantage of the fact that console.log is not specified by ECMA is interesting in its own right. But the varying behavior, and the fact that you have to be aware of whether your callback uses this is important and means we have to fall back to direct coding, even if it is verbose thanks to the keyword function.

Ray Toal
  • 86,166
  • 18
  • 182
  • 232
  • Thanks for this explanation - readers might find the following link which describes the expected arguments to Array.prototype.forEach(), and the args passed to the supplied callback (as you discussed): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach – arcseldon Dec 15 '15 at 12:33
  • 1
    Though with arrow functions you can use: `[1,2,3].forEach(v=>console.log(v))`. ;-) – RobG Nov 26 '16 at 21:01
  • Indeed, I can’t believe I was still using the long-form function expression as arguments to `forEach` (and friends) in early 2014. Must have switched over shortly after, though. – Ray Toal Nov 27 '16 at 07:54
  • This is still valid as of the current LTS version of node (v16). Seems like it will stay like this forever? – M Imam Pratama Feb 08 '22 at 06:33
  • Are you referring to the fact that JS allows both arrow and non-arrow functions? Yes they will co-exist forever because they are very different creatures, though in this particular case (since there is no `this` involved, essentially), either can be used but the arrow form is preferred. But in most cases they are not the same: methods require non-arrow functions and callbacks within methods should be arrow-functions, to cite the most common example. – Ray Toal Feb 08 '22 at 06:58
13

This works:

[1,2,3,4,5].forEach(console.log.bind(console));
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • 3
    great answer, I'd like to mark it as accepted but could you explain (for those who don't know) what your code does? If not I'm afraid I'll have to accept mine which is a bit more informative. Thanks – Pablo Fernandez Mar 09 '12 at 20:50
  • 2
    Does not work in Node v6: > [1,2,3].forEach(console.log.bind(console)) 1 0 [ 1, 2, 3 ] 2 1 [ 1, 2, 3 ] 3 2 [ 1, 2, 3 ] – Avindra Goolcharan Jun 06 '16 at 13:35
  • Still failing in Node v7. I would avoid this solution if you want simply to log the elements of the array. This answer applies `console.log` to the three parameters of the callback and prints out very messy and undesirable output. It probably did work back when the answer was written though, but only because the particular JavaScript implementation was wrong, IMHO. The callback takes three parameters these days. – Ray Toal Nov 27 '16 at 07:58
10

Actually as @SLaks pointed out, console.log seems to be using this internally and when it's passed as a parameter this now refers to the array instance.

The workaround for that is simply:

var c = console.log.bind(console); [1,2,3,4,5].forEach(c);

Pablo Fernandez
  • 103,170
  • 56
  • 192
  • 232
-5

I can't say I've seen that syntax, but my guess is because log expects a parameter, being the message/object/etc to log in the console.

in the first example, you are just passing a function reference to forEach, which is fine if your function doesn't expect paramater which make the function behave as expected. In the second example you pass in e and then log it.

thescientist
  • 2,906
  • 1
  • 20
  • 15
  • 3
    Wrong; `forEach` is passing a parameter. – SLaks Mar 09 '12 at 18:36
  • the point I was trying to make though is that the parameter needs to be passed to console.log though, i.e. console.log(_e_) – thescientist Mar 09 '12 at 18:37
  • 2
    Yes, and that's exactly what happens. There is no difference between a function from a function expression and a function from a property. – SLaks Mar 09 '12 at 19:08