How does `Array.prototype.slice.call` work?
What happens under the hood is that when .slice()
is called normally, this
is an Array, and then it just iterates over that Array, and does its work.
How is this
in the .slice()
function an Array? Because when you do:
object.method();
...the object
automatically becomes the value of this
in the method()
. So with:
[1,2,3].slice()
...the [1,2,3]
Array is set as the value of this
in .slice()
.
But what if you could substitute something else as the this
value? As long as whatever you substitute has a numeric .length
property, and a bunch of properties that are numeric indices, it should work. This type of object is often called an array-like object.
The .call()
and .apply()
methods let you manually set the value of this
in a function. So if we set the value of this
in .slice()
to an array-like object, .slice()
will just assume it's working with an Array, and will do its thing.
Take this plain object as an example.
var my_object = {
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three',
'4': 'four',
length: 5
};
This is obviously not an Array, but if you can set it as the this
value of .slice()
, then it will just work, because it looks enough like an Array for .slice()
to work properly.
var sliced = Array.prototype.slice.call( my_object, 3 );
Example: http://jsfiddle.net/wSvkv/
As you can see in the console, the result is what we expect:
['three','four'];
So this is what happens when you set an arguments
object as the this
value of .slice()
. Because arguments
has a .length
property and a bunch of numeric indices, .slice()
just goes about its work as if it were working on a real Array.
Array.prototype.slice.call(arguments) vs. Array.prototype.slice.apply(arguments)
If you'd like to pass arguments to slice in array instead of one by one, then there is a difference. You could do it this way
[1, 2, 3, 4, 5, 6, 7] ---- our example arguments
Array.prototype.slice.call(arguments, 2, 5);
here you pass to slice method 2 and 5 and these are slice arguments to indicate that you want to get items at index 1 to item at index. 4. So most likely it will be 3, 4, 5.
Array.prototype.slice.apply(arguments, [2, 5]);
This does the same but arguments for slice can be passed in an array.
What is the purpose of calling Array.prototype.slice against a NodeList?
What is the purpose of calling
Array.prototype.slice
against a NodeList?
The Array#slice
method "returns a shallow copy of a portion of an array into a new array object".
The Function#call
method "calls a function with a given this value and arguments provided individually".
Because Arrays are Objects, all Object property names are stored as strings, and all NodeLists store their elements with sequential numbered property names (again stored as strings), NodeLists can be used as the this
value for Array methods.
Creating a shallow copy of the NodeList as an Array allows you to use other Array methods on the newly created Array without using Function#call
.
Many modern browsers have implemented NodeList#forEach
, though the method is still a candidate recommendation and has not made it into the specification at this point. I recommend using that method with care, and not on the open web, until it has reached a more stable status.
Here are some other examples of Array methods being called with a NodeList as the target:
// Convert to an array, then iterate
const nodeArray = Array.prototype.slice.call(nodeList)
nodeArray.forEach(doSomething);
// Iterate NodeList directly without conversion
Array.prototype.forEach.call(nodeList, doSomething);
// Perform operation on each element in NodeList, output results to a new Array
Array.prototype.map.call(nodeList, function(item) {
return item;
}).forEach(doSomething);
// Filter NodeList, output result to a new Array
Array.prototype.filter.call(nodeList, function(item) {
return /* condition */;
}).forEach(doSomething);
There are many other ways that you can iterate a NodeList that don't require the use of Array methods, here are some more examples:
You can use a good old fashioned for loop, start at zero and loop until we reach the end of the array. This method has been around for ever and is still used regularly today. This method is, somewhat, less readable than other methods mentioned here, but it all comes down to what is easier for you to write.
for(let i = 0; i < nodeList.length; ++i) doSomething(nodeList[i]);
Using a backwards loop (where possible) can save reduce execution time due to a lack of conditional evaluation. In fact, some IDE's will convert the previous loop to the following structure by default.
for(let i = nodeList.length; i--;) doSomething(nodeList[i]);
You can use a while loop, which expects a conditional statement as its parameter. If NodeList.item(n)
is past the bounds of the NodeList it will return null, which will end the loop.
let i = 0, node;
while((node = nodeList.item(i++))) doSomething(node);
You can do the same thing with a for loop in the conditional:
let node;
for(let i = 0; (node = nodeList.item(i)); i++) doSomething(node);
You can use a for...in loop with Object.keys()
. Note that you have to use Object.keys
when using a for...in loop because otherwise it will iterate over the non-enumerable properties as well as the enumerable ones.
The Object.keys() method returns an array of a given object's own enumerable properties, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).
From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
for(var i in Object.keys(nodeList)) doSomething(nodeList[i]);
You can use a for...of
loop (ECMAScript 2015+) by retrieving the Iterator function from Array() and applying it to the NodeList. This will work for most other uses of an object as well, as long as the properties are enumerable.
nodeList[Symbol.iterator] = [][Symbol.iterator];
for(node of nodeList) doSomething(node);
If you apply the Array Iterator to the prototype of the NodeList class then whenever a new instance of NodeList is created, it will always be iterable. However, it is not advisable to extended native prototypes.
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
for(node of nodeList) doSomething(node);
What does Array.prototype.slice.call() & wrapper.querySelectorAll() do?
querySelectorAll
is a method on DOM elements that accepts a CSS selector and returns a static NodeList
of matching elements.
Array.prototype.slice.call
is one way to turn that NodeList
(which acts like an array, but doesn’t have the methods from Array.prototype
) into a real array.
Give it a try on this page in your browser’s console!
> var headers = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
undefined
> headers.map(function(el) { return el.textContent; })
TypeError: Object #<NodeList> has no method 'map'
> headers = Array.prototype.slice.call(headers);
…
> headers.map(function(el) { return el.textContent; })
["What does Array.prototype.slice.call() & wrapper.querySelectorAll() do?", …]
MDN JS Array.prototype.slice.call example explanation
Why doesn't it need to be invoked? — because, as you say, it's a reference, and that's all that's desired. The code wants a reference to the function, not a result returned from calling the function.
Is JS 'seeing' the call method chained to slice and then trying to resolve a value to pass to slice from the call(arguments) method? — well I'm not sure what that means. The reference to the
.slice()
function is used to get access to the.call()
method (inherited from the Function prototype). That function (slice.call
) is invoked and passed thearguments
object as its first parameter. The result is thatslice
will be invoked as if it were called likearguments.slice()
— which is not possible directly, as the.slice()
function isn't available that way.
Overall, what the code is doing is "borrowing" the .slice()
method from the Array prototype and using it as if the arguments
object were an array.
Sometimes you'll see that written like this:
return [].slice.call(arguments);
It's a little shorter, and it does the same thing (at the expense of the creation of an otherwise unused array instance).
arguments vs Array.prototype.slice.call(arguments,0)
The difference is that arguments
is an "array-like" object, not an array.
You can convert the arguments
object to a real array by slicing it, like so
Array.prototype.slice.call(arguments, 0);
This gives you an array, with array properties like forEach
, pop
etc. which objects like arguments
don't have (except length, which arguments
do have).
It is generally (almost) never a good idea to slice the arguments
object, MDN gives the warning
You should not slice on arguments because it prevents optimizations in
JavaScript engines (V8 for example). Instead, try constructing a new
array by iterating through the arguments object.
Also there should be no real need to pass arguments to a function, only to return them.
Internal working of Array.prototype.slice.call()
Array.prototype.slice.call()
requires a .length
property and then a corresponding zero-based number of numeric properties (though all property names are actually strings so it's the string equivalent of numbers). If it doesn't find those present, it does not do the .slice()
properly.
If the source is not an actual array, then it basically just goes into a loop from 0
to .length - 1
look for matching property names on the object and copies any value it finds with that property name. The property names must match the string equivalent of the numbers exactly.
Any missing numeric properties are simply copied as undefined
since that's what their value is.
[].slice.call vs Array.prototype.slice.call
Please refer to existing benchmarks
Like this one on jsperf.com I found typing "`[].slice performance" on google.
Revision 15 of the same benchmark also provides a wide variety of approaches, while revision 12 was also interesting for me.
What code to use
As @Barmar pointed out on comments, [].slice.call
is shorter than Array.prototype.slice.call
, so it's pretty common to see the former.
As @t.niese pointed out on comments, [].slice.call
creates a object that is never used, even though it most likely would not have noticeable performance impacts.
IMHO, if worried about performance, I prefer creating a shortcut with bind
on an outer scope and then use it, instead shortcuting Array.prototype.slice.call
with [].slice.call
:
var slice = Function.prototype.call.bind( Array.prototype.slice );
// and then just
function doSomething( ){
slice( arguments );
slice( arguments, 1, -1 );
}
slice( whatever );
slice( whatever, 0, 3 );
// and so on
Conclusions
Definitely, as seen on benchmarks, performance is not the same.
When performance really matters, just benchmark your code to optimize it according to your requeriments.
When performance does not matter enought to worry about a small improvement like this one, it's a code style related decision, so pick your personal preference or follow the code style guide of the project you are working at.
Side note
@Barmar posted a link on comments about Premature Optimization really interesting too.
JavaScript call() and Prototype - Slice Function
tl;dr:
var slice = Function.prototype.call.bind(unboundSlice);
is a short way of writing:
var slice = function(value, start, end) {
return unboundSlice.call(value, start, end);
};
Let's think about this line for second:
Array.prototype.slice.call(arguments)
.slice
is an array method to extract a subset of the array. It operates on the value of this
. .call
is a method every function has, it lets you set the this
value for a function execution. So, the above line lets us execute slice
as a method of arguments
, without having to mutate arguments
itself. We could have done
arguments.slice = Array.prototype.slice;
arguments.slice();
but that is not as clean.
Now looking at
Function.prototype.call.bind(unboundSlice);
As said, .call
is a method that every function has. It also operates on this
, which is expected to be a function. It calls this
and sets the this
value of that function to the first argument. You could think of call
as being similar to
function call(thisValue, arg1, arg2, ...) {
return this.apply(thisValue, [arg1, arg2, ...]);
}
Note how it calls this
as a function.
.bind
is also a method every function has. It returns a new function which has its this
value fixed to the first argument you pass in.
Let's consider what the resulting function of call.bind(unboundSlice)
would look like:
function boundCall(thisValue, arg1, arg2, ...) {
return unboundSlice.apply(thisValue, [arg1, arg2, ...]);
}
We simply replaced this
with unboundSlice
. boundCall
will now always call unboundSlice
.
Related Topics
Download Canvas as Png in Fabric.Js Giving Network Error
Get the Text Content from a Contenteditable Div Through JavaScript
Formdata.Append("Key", "Value") Is Not Working
How to Automatically Set the Focus to a Textbox When a Web Page Loads
How to Use JSON File in HTML Code
Regex to Match All Instances Not Inside Quotes
What Does the "|" (Single Pipe) Do in JavaScript
Vuejs Error: the Client-Side Rendered Virtual Dom Tree Is Not Matching Server-Rendered
Running JavaScript in Selenium Using Python
Addeventlistener Not Working in IE8
Firing Event on Dom Attribute Change
How Does Basic Object/Function Chaining Work in JavaScript
Casperjs/Phantomjs Doesn't Load Https Page