Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this throw an undefined exception?

Tags:

javascript

var x = []
var y = []
x.forEach (y.push)

Line 3 will throw Uncaught TypeError: Array.prototype.push called on null or undefined if x is non-empty.

var x = [ 1 ]
var y = []
x.forEach (y.push)

Notes: x.forEach passes 3 arguments to y.push, 1 of which is y. Explicitly doing this does not cause an error.

y.push (1, 1, y)

Wrapping y.push inside an anonymous function will prevent the error, even if all arguments are passed through. As far as I can tell, it doesn't matter the number of arguments applied.

x.forEach (function (a, b, c, d, e, f, g, h, i, j) {
  y.push (a, b, c, d, e, f, g, h, i, j)
})

Explicitly calling y.push with null and undefined will not cause the error, even when attempting to trick it with additional optional parameters.

y.push (1, 2, 3, undefined, 5, 6, null, 7, 8)
like image 248
user3288049 Avatar asked Feb 09 '23 07:02

user3288049


1 Answers

Because when you pass y.push to .forEach(), it loses the y context and you get just the push method with an inappropriate value for this when .forEach() calls it.

You can make it work by binding the object like this:

var x = [];
var y = [];
x.forEach (y.push.bind(y)); // push the x items into y

This, of course has other issues in that forEach() passes more than one argument which .push() will pick up. So, you really can't successfully use this short form to combine arrays.


When you do something like

var y = [];
var m = y.push;

At this point, m === Array.prototype.push. There is no object bound to the method in m. This is a subtlety of Javascript that trips up many, but when you really understand what's going in internally, it does start to make sense.

.push() is just a property on an object, so when you put that property into a variable or pass it as an argument, it's just its own value at that point an no longer has any association with the object that you got it from.

So, to use it appropriately after you grabbed it from an object, you have to bind it to an object with something like .call(), .apply() or .bind() in order to use it on that object.


If all you're trying to do is to copy all the x elements onto the end of y, there are a number of ways to do that. You can do this:

y.push.apply(y, x);

Since .push() takes more than one argument, you can just pass the whole x array to .push().

In ES6, this is even easier:

y.push(...x);
like image 87
jfriend00 Avatar answered Feb 11 '23 23:02

jfriend00