Setting Our Own `this`

Recall that functions, objects, and thethiskeyword are all interconnected. Whether you're invoking a constructor function with thenewoperator, invoking a method on an object, or simply invoking a function normally -- each form of invocation sets the value ofthisa bit differently.

But what if we want to set the value ofthisourselves? JavaScript offers a few methods that can do just that!

VIDEO

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 specifiedthisvalue, 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 themultiplypreceding.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 havemockingbirdinvoke its owndescribe()method:

mockingbird.describe();


// 'To Kill a Mockingbird is a classic novel'

Usingcall(), however, the followingprideobject 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 ofthisis passed into thecall()method:pride.

Sincemockingbird'sdescribe()method referencesthis.title, we need to access thetitleproperty of the object thatthisrefers to. But since we've set our own value ofthis, the value ofthis.titlewill be accessed from theprideobject! 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 ofthisto).

Now what about invoking an object's method withapply()? Recall the previousmockingbirdandprideobjects:

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 theprideobject 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!

VIDEO

Here's the code from the preceding video.

QUESTION 1 OF 5

Consider the followingdaveobject, 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 setthisproperly.

SUBMIT: The main question here is: how do we setthisto thedaveobject ifsayHello()is a function (i.e., not a method)? We can useapplycan do just that that! Keep in mind thatapplytakes in two arguments: the object thatthisshould 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 usedcallto 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 followingAndrewandRichardobjects:

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 theRichardobject'sintroduceproperty 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 withthisset to theAndrewobject.

As a result, whenthis.nameis called, theAndrewobject'snameis 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 ofthishas some potential scope issues when _callback functions _are involved, and things can get a bit tricky. Let's check it out below.

VIDEO

Here's the code from the preceding video.

Savingthiswith an Anonymous Closure

Let's recap the issue at hand. Here's theinvoiceTwice()function from the previous video, as well as thedogobject:

function invokeTwice(cb) {
   cb();
   cb();
}

const dog = {
  age: 5,
  growOneYear: function () {
    this.age += 1;
  }
};

First, invokinggrowOneYear()works as expected, updating the value of thedogobject'sageproperty from5to6:

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 theageproperty indogto 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 thethisgrid from earlier:

If a constructor function is called with thenewoperator, the value ofthisis set to the newly-created object. If a method is invoked on an object,thisis set to that object itself. And if a function is simply invoked,thisis set to the global object:window.

Savingthiswith an Anonymous Closure

Recall that simply invoking a normal function will set the value ofthisto the global object (i.e.,window). This is an issue, because we wantthisto be thedogobject!

So how can we make sure thatthisis preserved?

One way to resolve this issue is to use ananonymous closureto close over thedogobject:

invokeTwice(function () { 
  dog.growOneYear(); 
});

dog.age;
// 7

Using this approach, invokinginvokeTwice()still sets the value ofthistowindow. However, this has no effect on the closure; within the anonymous function, thegrowOneYear()method will still be directly called onto thedogobject! As a result, the value ofdog'sageproperty increases from5to7.

Since this is such a common pattern, JavaScript provides an alternate and less verbose approach: thebind()method.

Savingthiswith 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, hasthisset to the value we give it.

Let's see it in action!

VIDEO

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 function

  • bind()returns a new function that, when called, hasthisset to the provided object

  • The 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 fromdriverfor thecarobject 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 ofthisfor a given function:

  • call() invokes the function and has arguments passed in individually, separated by commas.
  • apply() is similar to call(); it invokes the function just the same, but arguments are passed in as an array.
  • bind() returns a new function with this 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 thethiskeyword 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

results matching ""

    No results matching ""