248

I stumbled onto this neat shortcut for converting a DOM NodeList into a regular array, but I must admit, I don't completely understand how it works:

[].slice.call(document.querySelectorAll('a'), 0)

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

The bit I don't understand is the call. How does that convert document.querySelectorAll('a') from a NodeList to a regular array?

Yansky
  • 4,580
  • 8
  • 29
  • 24
  • 12
    `Array.prototype.slice.call(document.querySelectorAll('a'));` is a proper way to write the chunk of code you wrote. – vdegenne Apr 03 '15 at 19:26
  • 13
    BTW, the modern (and intuitively understandable) ES6 method for the same is [`Array.from`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/from). So e.g. this would do the same: Array.from(document.querySelectorAll('a')); – rugk Apr 27 '19 at 14:11
  • Does this answer your question? [how does Array.prototype.slice.call() work?](https://stackoverflow.com/questions/7056925/how-does-array-prototype-slice-call-work) – Henke Mar 14 '21 at 16:59

9 Answers9

185

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.

Max Shawabkeh
  • 37,799
  • 10
  • 82
  • 91
  • 71
    Note too here that although this is semantically equivalent to saying `Array.prototype.slice.call(...)`, it actually instantiates an array object (`[]`) only to access its prototype slice method. That is a wasted instantiation. Saying `Array.prototype.slice.call(...)` instead is cleaner, although you add several characters to your JS if you're counting... – Ben Zotto Jan 24 '10 at 03:23
  • Note that this works in IE 8 and below only on Array objects, so you'll not be able to clone `NodeList`s – Livingston Samuel Mar 07 '11 at 13:40
  • 5
    @quixoto `[]` is more reliable since `Array` could be overwritten to something else. If you need to reuse `Array#slice`, it’s a good idea to cache it. – Mathias Bynens Mar 22 '11 at 14:52
  • 2
    In case anyone else is looking for a way to do this in IE8, check out this question http://stackoverflow.com/questions/3199588/fastest-way-to-convert-javascript-nodelist-to-array – Liam Newmarch Aug 02 '11 at 13:43
  • 1
    I actually saw this pattern appear in the backbone.js source code: `var array = []; var push = array.push; var slice = array.slice; var splice = array.splice;` Does he do this for the safety issue @MathiasBynens mentions? – owensmartin Jul 18 '14 at 17:39
  • @MathiasBynens I believe it would be anti-pattern to overwrite Array it self, worse than creating prototype functions. – Mantas Nov 09 '22 at 07:33
157

In JavaScript, methods of an object can be bound to another object at runtime. In short, javascript allows an object to "borrow" the method of another object:

object1 = {
    name: 'Frank',
    greet() {
        alert(`Hello ${this.name}`);
    }
};

object2 = {
    name: 'Andy'
};

// Note that object2 has no greet method,
// but we may "borrow" from object1:

object1.greet.call(object2); // Will show an alert with 'Hello Andy'

The call and apply methods of function objects (in JavaScript, functions are objects as well) allows you to do this. So, in your code you could say that the NodeList is borrowing an array's slice method. .slice() returns another array as its result, which will become the "converted" array that you can then use.

Cloud
  • 938
  • 1
  • 8
  • 24
slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Bang on abstract concept explanation for javascript object's functions. Now, you can apply it for `call` function of `Array.prototype` aka `[].prototype` yourself. – Sourabh Feb 24 '20 at 03:17
34

It retrieves the slice function from an Array. It then calls that function, but using the result of document.querySelectorAll as the this object instead of an actual array.

Brian Campbell
  • 322,767
  • 57
  • 360
  • 340
23

It is a technique to convert array-like objects to real arrays.

Some of these objects include:

  • arguments in functions
  • NodeList (remember their content can change after being fetched! so converting them to array is a way to freeze them)
  • jQuery collections, aka jQuery objects (some doc: API, type, learn)

This serves many purposes, for example objects are passed by reference whereas arrays are passed by value.

Also, note the first argument 0 can be omited, thorough explanation here.

And for the sake of completeness, there is also jQuery.makeArray().

Community
  • 1
  • 1
Gras Double
  • 15,901
  • 8
  • 56
  • 54
20

How does that convert document.querySelectorAll('a') from a NodeList to a regular array?

This is the code that we have,

[].slice.call(document.querySelectorAll('a'), 0)

Lets dismantle it first,

  []    // Array object
.slice // Accessing the function 'slice' present in the prototype of Array
.call // Accessing the function 'call' present in the prototype of function object(slice)
(document.querySelectorAll('a'),0) 
    // 'call' can have arguments like, (thisArg, arg1,arg2...n). 
   // So here we are passing the 'thisArg' as an array like object,
  // that is a 'nodeList'. It will be served as 'this' object inside of slice function.
 // And finally setting 'start' argument of slice as '0' and leaving the 'end' 
// argument as 'undefined'

Step: 1 Execution of call function

  • Inside call, other than the thisArg, the rest of the arguments will be appended to an argument list.
  • Now the function slice will be invoked by binding its this value as thisArg (array like object came from document.querySelector) and with the argument list. i.e] argument start which contains 0

Step: 2 Execution of slice function invoked inside of call

  • start will be assigned to a variable s as 0
  • since end is undefined, this.length will be stored in e
  • an empty array will be stored in a variable a
  • After making the above settings the following iteration will be happened

    while(s < e) {
      a.push(this[s]);
      s++;
    }
    
  • the filled up array a will be returned as the result.

P.S For better understanding of our scenario some steps that are necessary for our context has been ignored from the original algorithm of call and slice.

Rajaprabhu Aravindasamy
  • 66,513
  • 17
  • 101
  • 130
10
[].slice.call(document.querySelectorAll('.slide'));
  1. The querySelectorAll() method returns all elements in the document that matches a specified selector(s).

  2. The call() method calls a function with a given this value and arguments provided individually.

  3. The slice() method returns the selected elements in an array, as a new array object.

so this line return the array of [object HTMLDivElement]. Here is the six div with classname "slide" so array length will be 6.

var arraylist = [].slice.call(document.querySelectorAll('.slide'));
console.log(arraylist);
<div class="slideshow">
  <div class="slide">
    first slider1
  </div>
  <div class="slide">
    first slider2
  </div>
  <div class="slide">
    first slider3
  </div>
  <div class="slide">
    first slider4
  </div>
  <div class="slide">
    first slider5
  </div>
  <div class="slide">
    first slider6
  </div>
</div>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
Ankit Parmar
  • 670
  • 10
  • 24
8

From ES6: Simply make array with Array.from(element.children) or Array.from({length: 5})

Мони
  • 81
  • 2
  • 3
3

In the 2020s we use

[...document.querySelectorAll('.slide')]

It is useful when you want to use map or filter but no longer needed to use forEach since forEach now works on the collection returned from document.querySelectorAll('.slide')

mplungjan
  • 169,008
  • 28
  • 173
  • 236
0

This may help too.

slice method

Description:

slice does not alter the original array. It returns a shallow copy of elements from the original array. Elements of the original array are copied into the returned array.

The slice() method returns a shallow copy of a portion of an array into a new array object selected from start to end (end not included) where start and end represent the index of items in that array. The original array will not be modified. see more: Reference/Global_Objects/Array/slice

call method

Description:

The call() allows for a function/method belonging to one object to be assigned and called for a different object.

The call() method calls a function with a given this value and arguments provided individually. call() provides a new value of this to the function/method. With call(), you can write a method once and then inherit it in another object, without having to rewrite the method for the new object.

see more: Reference/Global_Objects/Function/call