Demystify Array.prototype.slice.call()
And everything you want to know about JavaScript bind, call, apply method
This is a revisit to an old topic —I admit. But it can be still confusing, until today. I remember when I first saw the usage of Array.prototype.slice.call()
— and that was the time when I didn’t grasp the concepts of prototype chain, magic “this” and method borrowing fully at all, it was like blending 10 vegetables in a fancy hipster smoothie and you have to tell the ingredients from looking at the colour without even have a sip of it.
Before we jump into Array.prototype.slice.call()
directly, there are a couple of concepts we need to understand first.
Firstly, the “This” .
This is special in JavaScript. It is a property of an execution context (global, function or eval) that, in non–strict mode, is always a reference to an object and in strict mode can be any value.
- In the global execution context
this
refers to the global object whether in strict mode or not. - Inside a function, the value of
this
depends on how the function is called. Since the following code is not in strict mode, and because the value ofthis
is not set by the call,this
will default to the global object, which iswindow
in a browser. In strict mode, however, if the value ofthis
is not set when entering an execution context, it remains asundefined
. - Within a class constructor,
this
is a regular object. All non-static methods within the class are added to the prototype ofthis
: - In arrow functions,
this
retains the value of the enclosing lexical context'sthis
. In global code, it will be set to the global object. - When a function is called as a method of an object, its
this
is set to the object the method is called on.
This is important, as all other concepts we will be discussing soon are derived from manipulating “This”.
Function.prototype.call()
The call()
method calls a function with a given this
value and arguments provided individually.
Its signature is: func.call([thisArg[, arg1, arg2, ...argN]])
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. This is particularly useful with describing inheritance relationship between classes:
In the following example, the constructor for the Product
object is defined with two parameters: name
and price
.
Two other functions, Food
and Toy
, invoke Product
, passing this
, name
, and price
. Product
initializes the properties name
and price
, both specialized functions define the category
.
function Product(name, price) {
this.name = name;
this.price = price;
}function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}const cheese = new Food('feta', 5);
const fun = new Toy('robot', 40);
Of course, the first context argument is optional. In the example below, we invoke the display
function without passing the first argument. If the first argument is not passed, the value of this
is bound to the global object in non-strict mode and undefined
in strict mode.
var sData = 'Wisen';function display() {
console.log('sData value is %s ', this.sData);
}display.call(); // sData value is Wisen========================>
'use strict';var sData = 'Wisen';function display() {
console.log('sData value is %s ', this.sData);
}display.call(); // Cannot read the property of 'sData' of undefined
Function.prototype.apply()
apply()
and call()
are very identical except that call()
accepts an argument list, while apply()
accepts a single array-like arguments.
Its signature is: func.apply(thisArg, [ argsArray])
What is array-like?
Some JavaScript objects, such as the NodeList
returned by document.getElementsByTagName()
or the arguments
object made available within the body of a function, look and behave like arrays on the surface in that they have indexed access and a length property, but do not share all of their methods.
A typical example is arguments
:
arguments[0] // first argument
arguments[1] // second argument
arguments[2] // third argument
The arguments
object is not an Array
. It is similar, but lacks all Array
properties except length
.
And finally, Function.prototype.bind()
The bind()
method creates a new function that, when called, has its this
keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
let boundFunc = func.bind(thisArg[, arg1[, arg2[, ...argN]]])
As you can see, the bind()
function creates a new bound function, which is an exotic function object (a term from ECMAScript 2015) that wraps the original function object. Calling the bound function generally results in the execution of its wrapped function.
A little on exotic function object:
All objects in JavaScript can be divided into two kinds: ordinary and exotic objects. The former has standard, predictable behavior. Exotic objects, on the other hand, might surprise you with its behaviour, good or bad. Some of the builtin object types that are exotic: Array, Proxy, String, Arguments, Module.
For example with Array, it has a length
property which automatically increases when indexed properties get added, and can have an impact on other properties when manipulated:
let arr = [];
arr.length; // 0
arr[0] = "a";
arr.length; // 1
arr.length = 0;
arr[0]; // undefined
The simplest use of bind()
is to make a function that, no matter how it is called, is called with a particular this
value. This is helpful when you want to use a function outside of the context it’s created but still need to access the internal state of its original context:
this.x = 9;
const module = {
x: 81,
getX: function() { return this.x; }
};module.getX(); // 81const retrieveX = module.getX;
retrieveX(); // 9
To still get x value to original 81, we need to bind the context of retrieveX to original module:
const boundGetX = retrieveX.bind(module);
boundGetX(); // 81
The differences of bind, call and apply
You may notice that call/apply
invokes the function immediately, while bind
returns a function that, when later executed, will have the context set earlier. It is particularly useful in maintaining context in async callbacks and events.
For apply and call, since one takes an argument list, the other takes an array-like arguments, you should pick apply
when you don’t know the exact number of arguments that the functions need so you can unpack the arguments easily:
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);console.log(max);
// 7
Note that in above example, the use of null
with .apply()
or .call()
is only done for namespace reasons, not for object-oriented reasons, as max()
is a method on the Math module only because of namespace , not because the Math object has instance data that the method .max()
needs to access, otherwise, call null
without a context that provides the instance data will raise error.
Another use case of bind
thanks to its “delayed calling”, is Partial.
Partial is a functional programming concept. A partial application refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
These fixed arguments (if any) follow the provided this
value and are then inserted at the start of the arguments passed to the target function, followed by whatever arguments are passed bound function at runtime.
To use the example from MDN:
function list() {
return Array.prototype.slice.call(arguments);
}function addArguments(arg1, arg2) {
return arg1 + arg2
}const list1 = list(1, 2, 3);
// [1, 2, 3]const result1 = addArguments(1, 2);
// 3// 37 is fixed
const leadingThirtysevenList = list.bind(null, 37);// 37 is fixed
const addThirtySeven = addArguments.bind(null, 37);const list2 = leadingThirtysevenList();
// [37]const list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]const result2 = addThirtySeven(5);
// 37 + 5 = 42const result3 = addThirtySeven(5, 10);
// 37 + 5 = 42
// (the second argument is ignored
A final remark
As we can see, regardless of calling it later or earlier, call
apply
and bind
are useful methods to enable re-using of built-in functions in different modules. We don’t need to specifically write a child class to inherit or extend from the parent class just to access one method. This is an important aspect of function borrowing, with one of the most popular native methods being Array.prototype.slice
Now let’s look at the Array.prototype.slice()
:
There are array-like objects that behave like arrays but lack the necessary methods for example arguments. For example we want to find if a list of arguments passed in contain letter “b”. Since we can’t simply call .filter()
on arguments
because it is not an array, we have to convert it into an array by borrowing the .slice
method from Array.prototype
, and setting this
context to the arguments
object:
function findB() {
var args = Array.prototype.slice.call(arguments)
return args.filter(a => a.includes('b'))
}
findO("bud", "bid", "bed", "bad")
=> [ 'bed' ]
That’s so much of it!
Happy Reading!