I recently ran into this lines of code in JavaScript:
[].slice.apply(arguments)
[].slice.call(document.querySelectorAll('foo'))
And although hard to read, this lines of code are so useful because they allow you to apple array specific methods to collections that not necessarily are arrays, such as NodeLists and the Arguments object.
But what are these lines of code doing and what can they tell us about the flexibility of JavaScript?.
The how
According with the W3Schools, the slice method allows you to select and copy elements from one array into a new array. So the first thing that came to my mind while reading this line of code [].slice.apply(arguments)
is, why are we calling slice in an empty array? If the purpose of this method is to select and copy elements from one array, how is it useful if we're calling it with an array that has no elements in it.
The thing is that this poor empty array is only exposing its precious slice method to be taken away. JavaScript allows you to change the context in which a method is called by using the apply and call methods. In other words, you can set the this
value in a method' body, through call and apply.
Remember that every JavaScript function is actually an Object, and a clear example of it is that every JavaScript function has a call and an apply method, among another handful of methods that you can call through the function definition.
In practical words, for every JavaScript function, you can call other methods such as apply and call, for example:
function foo(){}
foo.apply();
foo.call();
So what this line of code is doing [].slice.apply(arguments)
is that changes the context from the empty array, to the arguments object, to make it seem that the object that called slice
was arguments
and not the empty array itself.
What makes this work is that the arguments object has some properties that are similar to the ones that arrays have, such as length, and these similarities allows the slice method to work with both arguments and arrays as context.
After the first argument, the apply method allows you to send the arguments that will be passed as the arguments to the function call, in this case when calling the slice method, since in this case we're only passing one argument, the arguments object, the slice method receives no arguments, which according with the documentation means that all the array elements will be copied into the new array, which in resume means that this line of code copies all the elements from the arguments object into a new array.
Now, talking about readability, this methods does exactly the same that the Array.from
ES6 method does.
Array.from(arguments) === [].slice.apply(arguments)
The why
This particular line of code [].slice.apply(arguments)
allows us to easily convert an object that looks and possible behaves like an array but that is not array, another common example aside from the arguments object is the NodeList objects such as the ones returned from the querySelectorAll
function.
This is super useful because it allows us to play with the collection as an array, we can then plug the result into a loop, using it in combination with other array methods such as concat, includes, etc.
Conclusion
What this line of code let me thinking about after reading it, was in how JavaScript allows us to call methods from objects that do not posses these methods in its prototype, which I believe fits into the duck typing philosophy that languages such as Ruby have made so popular.
So, as long as an object can provide the properties and methods for a method to work, you can set this object as the context of the method to effectively call a method from an object that does not have that object.
So, what do you think of the way JavaScript works in these scenarios? Let me know in the comments.
I'd also appreciate if you can report grammar errors or typos to me since english ain't my native language. Thanks in advance!
Top comments (4)
While you aptly lift the lid on some of the inner flexibility of JavaScript, you also acknowledge right at the start that it's "hard to read". Which to me implies "for academic use only". This is not intended as a criticism, just a comment.
Constructs like this should carry a safety warning, as the risk to production code is of making the whole thing "write-only". Pity the poor maintenance engineer, 5 years down the line, dealing on a daily basis with JavaScript (and its fleet of frameworks), Python, Java and legacy PHP, with limited experience of but responsibility for all of these and much more, who comes across such a construct and has to make sense of it. It's essentially an undocumented feature, in that few know about it and clear explanations of how it works are hard to find.
JavaScript probably has more unusual, powerful and arcane constructs than any other language, including many that are unintended side-effects of other features. They're great for the classroom and for coding challenges, so let's keep them there.
This is a pretty popular line of code that until ES6 allowed you to manipulate array like objects as arrays so, it's not just an academic example, it's a real use case of the flexibility of the language.
It's also important that as the language evolves, there are now more readable alternatives.
I believe that understanding JavaScript it's important because it allows you to embrace the way it works.
Well I have to allow you that. Perhaps I came over as being a bit hostile, but I had no wish to. Just because I personally have a negative reaction to certain constructs doesn't mean they should be banned. I can't tell if others look at
[].slice.apply(arguments)
and experience the same feeling I do of my brain freezing over, but I operate on the principle that if something needs explaining in detail then I will avoid it if my code is to be read by people at or below my own level of ability.The benefit to me of articles like this are they help me benchmark myself and establish an eclectic style that suits me and the likely readers of my code.
As for "more readable alternatives" - that has a thumbs-up from me, though the designers of JS are finding it increasingly harder to come up with ways to achieve this when constrained by a restricted syntax. It's not like in English, where we seamlessly add "laser" to the language to avoid saying "Light Amplification by Stimulated Emission of Radiation" every time. But don't get me started on linguistics.
Yep, that is absolutely cool (and terrifying?) at the same time. You can also see that with custom methods this can open up new opportunities for reuse. Call in Arrays and strings especially find a sweet-spot - thanks to their similarities, but the principle goes into multiple ways of using functions and currying functions.