Nowadays, we can convert a NodeList
into a true array by using the modern Array.from()
method. But this requires a polyfill for IE, so I still use the older Array.prototype.slice.call()
method sometimes. It's a lot harder to read, though, so let's dig into how it works!
The slice()
method
Normally, you call the slice()
method directly on an array instance. It copies a segment of an array into a brand new array:
var fruits = ["Apples", "Bananas", "Cherries"];
var bananasAndCherries = fruits.slice(1);
You can also create a complete copy of an array by calling the slice()
method without any arguments:
var fruits = ["Apples", "Bananas", "Cherries"];
var fruitsCopy = fruits.slice();
This is the important part.
All array instances inherit the slice()
method through their prototype chain. Don't worry too much about this technical term, but if you're interested, you can learn more in the MDN Web Docs:
Learn more: Inheritance and the prototype chain.
The call()
method
The call()
method is available to all functions via their prototype chain.
As explained in the MDN Web Docs:
The
call()
method calls a function with a giventhis
value and arguments provided individually.
Essentially, binding a different value for this
changes the scope of the function.
Changing the scope lets you call the function on an object to which it would not normally be available.
Putting it all together
Let's look at the following example:
// Get all paragraphs as a NodeList
var paragraphs = document.querySelectorAll("p");
// Convert the NodeList into an array
paragraphs = Array.prototype.slice.call(paragraphs);
First, we get all paragraphs on the page using the querySelectorAll()
method.
This returns a NodeList
, though.
As I mentioned above, the slice()
method is only available to array instances. Our NodeList
cannot access it directly.
So we can't just call paragraphs.slice()
to create a copy of the NodeList
as a brand new array. We'd get a TypeError
if we tried:
TypeError: paragraphs.slice is not a function
Remember that the call()
method allows us to change the scope of a function, so we can call it on an object to which it would not normally be available?
Normally, when you call the slice()
method directly on an array instance, its internal this
value points to that array instance.
But here, we change the this
value so that it points to the NodeList
we have stored in the paragraphs
variable:
paragraphs = Array.prototype.slice.call(paragraphs);
This successfully creates a brand new copy of the NodeList
as an array, so all the array instance methods are now available to us!
Further reading
The this
keyword and the Function.prototype.call()
method can be really confusing. Even seasoned developers struggle with them.
If you want to learn more, I recommend the following:
- What the lexical is this? by Steve Griffith
- Everything you wanted to know about JavaScript scope by Todd Motto
If none of this makes sense, seriously, don't worry about it. You can still use the Array.prototype.slice.call()
trick safely.
I've been working with JavaScript for several years now, and I've only just started to feel more comfortable with these concepts.