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);
.
15 Answers
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.
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.
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
.
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);
// 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]
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);
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.
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.
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.
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"]
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
/*
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
I'm just writing this to remind myself...
Array.prototype.slice.call(arguments);
== Array.prototype.slice(arguments[1], arguments[2], arguments[3], ...)
== [ arguments[1], arguments[2], arguments[3], ... ]
Or just use this handy function $A to turn most things into an array.
function hasArrayNature(a) {
return !!a && (typeof a == "object" || typeof a == "function") && "length" in a && !("setInterval" in a) && (Object.prototype.toString.call(a) === "[object Array]" || "callee" in a || "item" in a);
}
function $A(b) {
if (!hasArrayNature(b)) return [ b ];
if (b.item) {
var a = b.length, c = new Array(a);
while (a--) c[a] = b[a];
return c;
}
return Array.prototype.slice.call(b);
}
example usage...
function test() {
$A( arguments ).forEach( function(arg) {
console.log("Argument: " + arg);
});
}