How Does 'Array.Prototype.Slice.Call' Work

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

  1. 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.

  2. 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 the arguments object as its first parameter. The result is that slice will be invoked as if it were called like arguments.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



Leave a reply



Submit