How does `Array.prototype.slice.call` work?
Asked Answered
W

15

529

I know it is used to make arguments a real Array, but I don‘t understand what happens when using Array.prototype.slice.call(arguments);.

Wheeled answered 14/8, 2011 at 12:59 Comment(1)
^ slightly different since that link asks about DOM nodes and and not arguments. And i think the answer here is much better by describing what 'this' is internally.Nidify
G
927

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.

Gromwell answered 14/8, 2011 at 13:33 Comment(17)
Great answer! But sadly you can't convert just any object this way, if your object keys are string values, like in actual words.. This will fail, so keep your objects content as '0':'value' and not like 'stringName':'value'.Bunyan
Where can I found the source for this answer? I mean, where did you found it? Is there any resource that I cand get to in order to read the slice() function body? In console, for instance, it says "native code"..Dingle
@Michael: Reading source code of the open source JS implementations is possible, but it's simpler to just refer to the "ECMAScript" language specification. Here's a link to the Array.prototype.slice method description.Youngs
Instead of calling the Array's prototype, you can also create an empty array and use it's .slice.call() function ::::: var sliced = [].slice.call(my_object, 3); ::::: jsfiddle.net/8k9R7Weed
since object keys has no order, this specific demonstration might fail in other browsers. not all vendors sort their objects' keys by order of creation.Laurynlausanne
@vsync: It's the for-in statement that doesn't guarantee order. The algorithm used by .slice() defines a numeric order starting with 0 and ending (non-inclusive) with the .length of the given object (or Array or whatever). So the order is guaranteed to be consistent across all implementations.Gutbucket
@cookiemonster - I do not understand your answer. what do you think Array.prototype.slice.call does internally? again, you assume it will give you back an ordered Array. of course, it shouldn't, but in real life, some browser vendors had forced it to act like so.Laurynlausanne
@vsync: It isn't an assumption. You can get order from any object if you enforce it. Let's say I have var obj = {2:"two", 0:"zero", 1: "one"}. If we use for-in to enumerate the object, there's no guarantee of order. But if we use for, we can manually enforce the order: for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Now we know that the properties of the object will be reached in the ascending numeric order we defined by our for loop. That's what .slice() does. It doesn't care if it has an actual Array. It just starts at 0 and accesses properties in an ascending loop.Gutbucket
...see the link to the spec in the comment above. It spells out how the properties of the object are accessed. It's a guaranteed order because they don't use for-in semantics. In other words, .slice() doesn't rely on the order of creation. They could be totally mixed up like my short example above, and it would still work.Gutbucket
Great explanation. Would help to add a bit about why simply calling my_object.slice(3) or my_object.prototype.slice(3) or Array.slice directly doesn't work.Two
Personally, I hate seeing this type of cryptic programming.Bernardinebernardo
The answer talking about the ES6 spread from Alexander and the answer from Marco Roy provide a better picture. The ES6 version describes the "modern" way of doing this. The Marco Roy answer describes how and why the OP's code works.Novelette
I didn't understand what slice.call() does.Malek
Related: How does the “this” keyword work?.Sorn
@DavidSpector Well, do you do now? The answer explains quite well what calling Array.prototype.slice.call does. Is there something specific about the answer that is unclear?Sorn
@Weed [].slice.call works, too, but is less clean because it creates a new array which is immediately discarded again.Sorn
@DanDascalescu my_object.slice and my_object.prototype.slice(3), of course, could work, if these properties actually led to the slice method. Maybe that could be added as an alternative approach to the answer, for educational purposes. The ECMAScript spec could theoretically introduce an arguments.slice in the future. Also, just for reference, “Array generics” such as Array.slice did exist for a short time but are deprecated now.Sorn
G
98

The arguments object is not actually an instance of an Array, and does not have any of the Array methods. So, arguments.slice(...) will not work because the arguments object does not have the slice method.

Arrays do have this method, and because the arguments object is very similar to an array, the two are compatible. This means that we can use array methods with the arguments object. And since array methods were built with arrays in mind, they will return arrays rather than other argument objects.

So why use Array.prototype? The Array is the object which we create new arrays from (new Array()), and these new arrays are passed methods and properties, like slice. These methods are stored in the [Class].prototype object. So, for efficiency sake, instead of accessing the slice method by (new Array()).slice.call() or [].slice.call(), we just get it straight from the prototype. This is so we don't have to initialise a new array.

But why do we have to do this in the first place? Well, as you said, it converts an arguments object into an Array instance. The reason why we use slice, however, is more of a "hack" than anything. The slice method will take a, you guessed it, slice of an array and return that slice as a new array. Passing no arguments to it (besides the arguments object as its context) causes the slice method to take a complete chunk of the passed "array" (in this case, the arguments object) and return it as a new array.

Gerhard answered 14/8, 2011 at 13:17 Comment(1)
You may want to use a slice for reasons described here: jspatterns.com/arguments-considered-harmfulZosema
G
51

Normally, calling

var b = a.slice();

will copy the array a into b. However, we can’t do

var a = arguments.slice();

because arguments doesn’t have slice as a method (it’s not a real array).

Array.prototype.slice is the slice function for arrays. .call runs this slice function, with the this value set to arguments.

Grantley answered 14/8, 2011 at 13:2 Comment(5)
thanx but why use prototype? isn't slice a native Array method?Wheeled
Note that Array is a constructor function, and the corresponding "class" is Array.prototype. You can also use [].sliceArtistic
IlyaD, slice is a method of each Array instance, but not the Array constructor function. You use prototype to access methods of a constructor's theoretical instances.Grantley
call() runs WHAT function?Malek
@DavidSpector I’ve edited to clarify that.Sorn
D
29

Array.prototype.slice.call(arguments) is the old-fashioned way to convert an arguments into an array.

In ECMAScript 2015, you can use Array.from or the spread operator:

let args = Array.from(arguments);

let args = [...arguments];
Deformity answered 28/7, 2017 at 10:49 Comment(2)
Do you mean that Array.slice.call(1,2) returns the value [1,2]? Why is it called "call" instead of "argsToArray"? Doesn't "call" mean to call a function?Malek
I see. Call means to call the function slice(), which is to an array what substr is to a string. Yes?Malek
B
25

First, you should read how function invocation works in JavaScript. I suspect that alone is enough to answer your question. But here's a summary of what is happening:

Array.prototype.slice extracts the slice method from Array's prototype. But calling it directly won't work, as it's a method (not a function) and therefore requires a context (a calling object, this), otherwise it would throw Uncaught TypeError: Array.prototype.slice called on null or undefined.

The call() method allows you to specify a method's context, basically making these two calls equivalent:

someObject.slice(1, 2);
slice.call(someObject, 1, 2);

Except the former requires the slice method to exist in someObject's prototype chain (as it does for Array), whereas the latter allows the context (someObject) to be manually passed to the method.

Also, the latter is short for:

var slice = Array.prototype.slice;
slice.call(someObject, 1, 2);

Which is the same as:

Array.prototype.slice.call(someObject, 1, 2);
Ballerina answered 19/12, 2015 at 0:17 Comment(2)
Why would one use .call() instead of bind()? Are they different?Malek
@DavidSpector : #15455509Ballerina
C
23
// We can apply `slice` from  `Array.prototype`:
Array.prototype.slice.call([]); //-> []

// Since `slice` is available on an array's prototype chain,
'slice' in []; //-> true
[].slice === Array.prototype.slice; //-> true

// … we can just invoke it directly:
[].slice(); //-> []

// `arguments` has no `slice` method
'slice' in arguments; //-> false

// … but we can apply it the same way:
Array.prototype.slice.call(arguments); //-> […]

// In fact, though `slice` belongs to `Array.prototype`,
// it can operate on any array-like object:
Array.prototype.slice.call({0: 1, length: 1}); //-> [1]
Cryptoclastic answered 20/8, 2013 at 0:10 Comment(0)
F
10

Its because, as MDN notes

The arguments object is not an array. It is similar to an array, but does not have any array properties except length. For example, it does not have the pop method. However it can be converted to a real array:

Here we are calling slice on the native object Array and not on its implementation and thats why the extra .prototype

var args = Array.prototype.slice.call(arguments);
Faxun answered 14/8, 2011 at 13:17 Comment(1)
Don't understand. Isn't Array the name of a class? Then calling a static method of the Array should be the same as calling the same method in Array.prototype, no?Malek
E
4

Dont forget, that a low-level basics of this behaviour is the type-casting that integrated in JS-engine entirely.

Slice just takes object (thanks to existing arguments.length property) and returns array-object casted after doing all operations on that.

The same logics you can test if you try to treat String-method with an INT-value:

String.prototype.bold.call(11);  // returns "<b>11</b>"

And that explains statement above.

Elan answered 3/2, 2012 at 3:2 Comment(1)
I tried this, and omitting the "prototype." results in an undefined error. Why? Doesn't the prototype hold all the methods, so they can be inherited by new objects?Malek
M
2

It uses the slice method arrays have and calls it with its this being the arguments object. This means it calls it as if you did arguments.slice() assuming arguments had such a method.

Creating a slice without any arguments will simply take all elements - so it simply copies the elements from arguments to an array.

Misapprehension answered 14/8, 2011 at 13:2 Comment(0)
T
2
Array.prototype.slice=function(start,end){
    let res=[];
    start=start||0;
    end=end||this.length
    for(let i=start;i<end;i++){
        res.push(this[i])
    }
    return res;
}

when you do:

Array.prototype.slice.call(arguments) 

arguments becomes the value of this in slice ,and then slice returns an array

Trimly answered 9/1, 2021 at 10:41 Comment(2)
You didn't say what the "call" is for. What function is call calling?Malek
I think it is calling "slice" which is supposed to be a function (like substr for strings) but is represented as an object. Yes?Malek
D
1

Let's assume you have: function.apply(thisArg, argArray )

The apply method invokes a function, passing in the object that will be bound to this and an optional array of arguments.

The slice() method selects a part of an array, and returns the new array.

So when you call Array.prototype.slice.apply(arguments, [0]) the array slice method is invoked (bind) on arguments.

Dinosaurian answered 14/8, 2011 at 13:3 Comment(1)
How does apply() differ from bind()?Malek
I
0

when .slice() is called normally, this is an Array, and then it just iterates over that Array, and does its work.

 //ARGUMENTS
function func(){
  console.log(arguments);//[1, 2, 3, 4]

  //var arrArguments = arguments.slice();//Uncaught TypeError: undefined is not a function
  var arrArguments = [].slice.call(arguments);//cp array with explicity THIS  
  arrArguments.push('new');
  console.log(arrArguments)
}
func(1,2,3,4)//[1, 2, 3, 4, "new"]
Ironsmith answered 29/1, 2015 at 16:51 Comment(0)
L
0

Maybe a bit late, but the answer to all of this mess is that call() is used in JS for inheritance. If we compare this to Python or PHP, for example, call is used respectively as super().init() or parent::_construct().

This is an example of its usage that clarifies all:

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

Reference: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance

Lori answered 22/7, 2017 at 17:33 Comment(0)
S
0
/*
    arguments: get all args data include Length .
    slice : clone Array
    call: Convert Object which include Length to Array
    Array.prototype.slice.call(arguments): 
        1. Convert arguments to Array
        2. Clone Array arguments
*/
//normal
function abc1(a,b,c){
    console.log(a);
} 
//argument
function: function abc2(){
    console.log(Array.prototype.slice.call(arguments,0,1))
}

abc1('a','b','c');
//a
abc2('a','b','c');
//a
Saharanpur answered 16/4, 2021 at 4:17 Comment(1)
It is better if you can provide some context to your answer so that others who go through your answer can easily understand the solution provided by you easily.Gook
C
0

When you think of .call (or .bind, or .apply), think of reusability. You want to reuse a function instead of writing it from scratch. Therefore:

  1. The goal with Array.prototype.slice(arguments) is just lending the slice functionality to the arguments object (which is not an array and doesn't have its own .slice).

  2. If you don't use .call, the this reference inside the slice function won't work properly (because it will point to nothing).

  3. So when you do Array.prototype.slice.call(arguments) you're reusing the slice function and instructing the runtime as to which object to use as context.

Try this (teaching a cat meow):

function Dog () {
  this.sound = 'rofl'
}

Dog.prototype.getSound = function () {
  console.log(this.sound)
} 

function Cat () {
  this.sound = 'meow'
}

const whiskers = new Cat();

Dog.prototype.getSound(whiskers); // undefined

Dog.prototype.getSound.call(whiskers); // "meow"
Camellia answered 24/8, 2023 at 19:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.