Wednesday 26 December 2012

One more JavaScript quirk

The other day, while happily adding one gist to my github gists, I stumpled upon something that had me stuck for a while. Indeed, it's not a JavaScript quirk, but either a Base Library quirk, or a lack of documentation reading on my side, or a combination of both... so the thing is that I thought I would share it here.

Long in short, be careful when using Array.concat with the arguments object, cause you could be expecting a different behaviour. By the way, I already talked about arguments here

Let's say you want to create a new array consisting of the elements in another array a1, and the arguments to that function... well, if you are used to .Net's Concat (and don't take into account that arguments is an "array like object", not a real array) you could end up with this wrong code:

function wrongTest(x, y){
var a1 = ["Xixon", "Berlin"];
 return a1.concat(arguments);
}
console.log(wrongTest("Prague", "Budapest").join(", "));

The code above won't print:
Xixón, Berlín, Prague, Budapest
but
Xixón, Berlín, [object Arguments]

So, what's happening here?
As you can see by reading the documentation

concat creates a new array consisting of the elements in the this object on which it is called, followed in order by, for each argument, the elements of that argument (if the argument is an array) or the argument itself (if the argument is not an array).

concat is a rather flexible beast, as it accepts as parameters much more than an obvious array. In our case, as I already said, arguments is not a real array... it just has a length property and allows indexed access, which is usually enough to allow us using on it the Array.prototype functions, but in this case, I guess concat is doing internally an instanceOf Array check, which obviously is not passed, and then is treated just as a plain object, concatenating the object itself instead of its elements.

This means you should rewrite the above code like this:

function correctTest(x, y){
 var a1 = ["Xixon", "Berlin"];
 return a1.concat(Array.prototype.slice.call(arguments, 0));
}
console.log(correctTest("Prague", "Budapest").join(", "));

Notice how we can leverage the array functions on arguments by means of the delegation mechanism provided by call. We're using slice here to obtain a real array with all the items in arguments.

You can verify that I'm not lying by running this code

No comments:

Post a Comment