Setting Our Own `this`
Recall that functions, objects, and thethis
keyword are all interconnected. Whether you're invoking a constructor function with thenew
operator, invoking a method on an object, or simply invoking a function normally -- each form of invocation sets the value ofthis
a bit differently.
But what if we want to set the value ofthis
ourselves? JavaScript offers a few methods that can do just that!
More Ways to Invoke Functions
We've seen various ways to invoke functions, each with their own implications regarding the value ofthis
. There are yet two more ways to invoke a function: either using thecall()
or theapply()
methods.
Each method can be directly invoked onto a function itself (after all, JavaScript functions are_first-class_functions and can have properties and methods). As a result, the_receiving_function will be invoked with a specifiedthis
value, as well as any arguments passed in.
Let's take a closer look at each of these methods, starting withcall()
!
call()
call()
is a method directly invoked onto a function. We first pass into it a single value to set as the value ofthis
; then we pass in any of the receiving function's arguments one-by-one, separated by commas.
Consider the following function,multiply()
, which simply returns the product of its two arguments:
function multiply(n1, n2) {
return n1 * n2;
}
Let's invoke it in the console:
multiply(3, 4);
// 12
No surprises here! But now -- let's use thecall()
method to invoke the same function:
multiply.call(window, 3, 4);
// 12
We get the same result! How did this happen? We first invoke thecall()
method directly onto themultiply()
function. Note that themultiply
preceding.call(window, 3, 4)
is not followed by any parentheses.call()
will be handling the invocation _and _themultiply()
function's arguments itself!
After writing that part, it's time to pass in the arguments! For the first argument of thecall()
method, we pass in the value to be set asthis
, which iswindow
. We then finish up by passing in themultiply()
function's arguments individually,separated by commas.
Oncemultiply.call(window, 3, 4);
executes, the function will be invoked with the given value ofthis
, and the result that we see is12
. Outside of strict mode, both ways of invokingmultiply()
above are equivalent.
Along with invoking regular functions, how do we go upon invoking functions attached to objects(i.e., methods)? This is where the power ofcall()
really shines. Usingcall()
to invoke a method allows us to "borrow" a method from one object -- then use it for _another _object! Check out the following object,mockingbird
:
const mockingbird = {
title: 'To Kill a Mockingbird',
describe: function () {
console.log(`${this.title} is a classic novel`);
}
};
We can havemockingbird
invoke its owndescribe()
method:
mockingbird.describe();
// 'To Kill a Mockingbird is a classic novel'
Usingcall()
, however, the followingpride
object can utilizemockingbird
'sdescribe()
method:
const pride = {
title: 'Pride and Prejudice'
};
mockingbird.describe.call(pride);
// 'Pride and Prejudice is a classic novel'
Let's break down what happened whenmockingbird.describe.call(pride);
is executed!
First, thecall()
method is invoked ontomockingbird.describe
(which points to a function). Then, the value ofthis
is passed into thecall()
method:pride
.
Sincemockingbird
'sdescribe()
method referencesthis.title
, we need to access thetitle
property of the object thatthis
refers to. But since we've set our own value ofthis
, the value ofthis.title
will be accessed from thepride
object! As a result,mockingbird.describe.call(pride);
is executed, and we see'Pride and Prejudice is a classic novel'
in the console.
call()
is very effective if you're looking to invoke a function in the scope of the first argument passed into it. Likewise, we can leverage theapply()
method to do the same, albeit with differences in how arguments are passed into it. Let's take a closer look!
apply()
Just likecall()
, theapply()
method is called on a function to not only invoke that function, but also to associate with it a specific value ofthis
. However, rather than passing arguments one-by-one, separated by commas --apply()
takes the function's arguments in an array. Recall themultiply()
function from earlier:
function multiply(n1, n2) {
return n1 * n2;
}
We usedcall()
and passed in arguments individually:
multiply.call(window, 3, 4);
// 12
Usingapply()
, however, we collect all of themultiply()
function's arguments in an array! Then, we pass that entire array intoapply()
:
multiply.apply(window, [3, 4]);
// 12
Great! Note that the first argument in bothcall()
andapply()
is stillwindow
(i.e., the object to bind the value ofthis
to).
Now what about invoking an object's method withapply()
? Recall the previousmockingbird
andpride
objects:
const mockingbird = {
title: 'To Kill a Mockingbird',
describe: function () {
console.log(`${this.title} is a classic novel`);
}
};
const pride = {
title: 'Pride and Prejudice'
};
Previously, we usedcall()
to allow thepride
object to "borrow"mockingbird
'sdescribe()
method:
mockingbird.describe.call(pride);
// 'Pride and Prejudice is a classic novel'
We can achieve the same result usingapply()
!
mockingbird.describe.apply(pride);
// 'Pride and Prejudice is a classic novel'
Note that the first argument passed into bothcall()
andapply()
is the same:pride
. Since thedescribe()
method doesn't take any arguments, the only difference betweenmockingbird.describe.call(pride);
andmockingbird.describe.apply(pride);
is just the method! Both approaches produce the same result.
Choosing One Method Over the Other
Bothcall()
andapply()
invoke a function in the scope of the first argument passed in them (i.e., the object to be the value ofthis
). So when would you choosecall()
overapply()
, or vice versa?
call()
may be limited if you don't know ahead of time the number of arguments that the function needs. In this case,apply()
would be a better option, since it simply takes an array of arguments, then unpacks them to pass along to the function. Keep in mind that the unpacking comes at a minor performance cost, but it shouldn't be much of an issue.
Let's now seecall()
andapply()
in action!
Here's the code from the preceding video.
QUESTION 1 OF 5
Consider the followingdave
object, and thesayHello()
function:
const dave = {
name: 'Dave'
};
function sayHello(message) {
console.log(`${message}, ${this.name}. You're looking well today.`);
}
Let's say you want the message'Hello, Dave. You're looking well today.'
printed to the console.
Which of the following expressions could you write to accomplish that?
dave.apply(sayHello('Hello'));
dave.sayHello('Hello');
sayHello.apply(dave, ['Hello']);
It is not possible.sayHello()
_must_be called as a method (i.e., invoked onto an object directly) in order to setthis
properly.
SUBMIT: The main question here is: how do we setthis
to thedave
object ifsayHello()
is a function (i.e., not a method)? We can useapply
can do just that that! Keep in mind thatapply
takes in two arguments: the object thatthis
should refer to, and an array of arguments meant to be passed into the function (i.e.sayHello()
). As suchsayHello.apply(dave, ['Hello']);
outputs the intended message:'Hello, Dave. You're looking well today.'
Note that we also could have usedcall
to solve this quiz. The expressionsayHello.call(dave, 'Hello');
produces the same message:'Hello, Dave. You're looking well today.'
QUESTION 2 OF 5
Consider the followingAndrew
andRichard
objects:
const Andrew = {
name: 'Andrew',
introduce: function () {
console.log(`Hi, my name is ${this.name}!`);
}
};
const Richard = {
name: 'Richard',
introduce: function () {
console.log(`Hello there! I'm ${this.name}.`);
}
};
WhenRichard.introduce.call(Andrew);
is executed, what is logged to the console?
'Hello there! I'm Andrew.'
'Hello there! I'm Richard.'
'Hi, my name is Richard!'
'Hi, my name is Andrew!'
SUBMIT: First, we access theRichard
object'sintroduce
property withRichard.introduce
(note the lack of parentheses). This returns a function. By invoking.call(Andrew)
on that returned function, we are actually invoking the (returned) function'sintroduce()
method -- but withthis
set to theAndrew
object.
As a result, whenthis.name
is called, theAndrew
object'sname
is accessed; this outputs'Hello there! I'm Andrew'
to the console.
Consider the following:
const andrew = {
name: 'Andrew'
};
function introduce(language) {
console.log(`I'm ${this.name} and my favorite programming language is ${language}.`);
}
Write an expression that uses thecall()
method to produce the message:'I'm Andrew and my favorite programming language is JavaScript.'
SUBMIT: introduce.call(andrew, 'JavaScript');
Callbacks andthis
The value ofthis
has some potential scope issues when _callback functions _are involved, and things can get a bit tricky. Let's check it out below.
Here's the code from the preceding video.
Savingthis
with an Anonymous Closure
Let's recap the issue at hand. Here's theinvoiceTwice()
function from the previous video, as well as thedog
object:
function invokeTwice(cb) {
cb();
cb();
}
const dog = {
age: 5,
growOneYear: function () {
this.age += 1;
}
};
First, invokinggrowOneYear()
works as expected, updating the value of thedog
object'sage
property from5
to6
:
dog.growOneYear();
dog.age;
// 6
However, passingdog.growOneYear
(a function) as an argument intoinvokeTwice()
produces an odd result:
invokeTwice(dog.growOneYear);
dog.age;
// 6
You may have expected the value of theage
property indog
to have increased to8
. Why did it remain6
?
As it turns out,invokeTwice()
does indeed invokegrowOneYear
-- but it is invoked as a function_rather than a_method! Let's revisit thethis
grid from earlier:
Savingthis
with an Anonymous Closure
Recall that simply invoking a normal function will set the value ofthis
to the global object (i.e.,window
). This is an issue, because we wantthis
to be thedog
object!
So how can we make sure thatthis
is preserved?
One way to resolve this issue is to use ananonymous closureto close over thedog
object:
invokeTwice(function () {
dog.growOneYear();
});
dog.age;
// 7
Using this approach, invokinginvokeTwice()
still sets the value ofthis
towindow
. However, this has no effect on the closure; within the anonymous function, thegrowOneYear()
method will still be directly called onto thedog
object! As a result, the value ofdog
'sage
property increases from5
to7
.
Since this is such a common pattern, JavaScript provides an alternate and less verbose approach: thebind()
method.
Savingthis
with bind()
Similar tocall()
andapply()
, thebind()
method allows us to directly define a value forthis
.bind()
is a method that is also called_on_a function, but unlikecall()
orapply()
, which both invoke the function right away --bind()
_returns_a new function that, when called, hasthis
set to the value we give it.
Let's see it in action!
Here's the code from the preceding video.
QUESTION 4 OF 5
What is true aboutbind()
? Select all that apply:
bind()
is a method that is called on a functionbind()
returns a new function that, when called, hasthis
set to the provided objectThe object passed intobind()
_must_be the same as the object on which the method was called (e.g.,dog.growOneYear.bind(dog);
)bind()
_invokes_a function, so it can be substituted withcall()
andapply()
SUBMIT: Under the hood, bind()
returns a new function that can be called like a normal function (e.g., myFunction()
), but inside of it, a method will be invoked method-style (e.g., myObject.myMethod()
). This has helps us when we see potential scope issues with this
when passing callback functions.
Consider the following:
const driver = {
name: 'Danica',
displayName: function () {
console.log(`Name: ${this.name}`);
}
};
const car = {
name: 'Fusion'
};
Write an expression usingbind()
that allows us to "borrow" thedisplayName()
method fromdriver
for thecar
object to use. Note: The_expression itself_is sufficient (no need to save it to a variable).
SUBMIT
Summary
JavaScript provides three methods that allow us to set the value ofthis
for a given function:
call()
invokes the function and has arguments passed in individually, separated by commas.apply()
is similar tocall()
; it invokes the function just the same, but arguments are passed in as an array.bind()
returns a new function withthis
bound to a specific object, allowing us to call it as a regular function.
For further research, we recommend checking out Kyle Simpson's You Don't Know JS series onthis
, linked below.
At this point, you've seen how functions, objects, and thethis
keyword are all very much interconnected. You've also seen how just about_everything_in JavaScript is an object!
Did you know that you can even base objects_on_other objects? This is the main idea behind prototypal inheritance, and by implementing it, objects can take on properties of other objects. We'll cover all this and more, coming up next!
Further Research
- Kyle Simpson's You Don't Know JS
- call() on MDN
- apply() on MDN
- bind() on MDN