Explanation of [].Slice.Call in JavaScript

Explanation of [].slice.call in javascript?

What's happening here is that you call slice() as if it was a function of NodeList using call(). What slice() does in this case is create an empty array, then iterate through the object it's running on (originally an array, now a NodeList) and keep appending the elements of that object to the empty array it created, which is eventually returned. Here's an article on this.

EDIT:

So it starts with an empty array [], then slice is used to
convert the result of call to a new array yeah?

That's not right. [].slice returns a function object. A function object has a function call() which calls the function assigning the first parameter of the call() to this; in other words, making the function think that it's being called from the parameter (the NodeList returned by document.querySelectorAll('a')) rather than from an array.

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.

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);

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

What is the slice.call() doing in get() jquery function

Your assessment is correct. The jQuery object is an array-like object and get is turning the jQuery object into a true array. This is outlined in the documentation.

For slice to work on an object other than an array, that object must have property keys that are integers. It must also have a length property:

Array.prototype.slice.call({ 0: 'foo', 1: 'bar', length: 2 })
// yields ['foo', 'bar']

Or more interestingly:

Array.prototype.slice.call({ 45: 'foo', 47: 'bar', length: 48 })
// [undefined × 45, "foo", undefined × 1, "bar"]

Why is array slice method called using call?

arguments is an array-like object, it is not an Array. As such, it doesn't have the slice method. You need to take the slice implementation from Array and call it setting its this context to arguments. This will return an actual Array, which is the whole point of this operation.

[].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.

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.



Related Topics



Leave a reply



Submit