DEV Community

Cover image for What the beep is 'this' in JavaScript
Brit Hemming
Brit Hemming

Posted on • Edited on

What the beep is 'this' in JavaScript

Watch on YouTube

link to codepen if you want to follow along

The 'this' keyword in JavaScript can be a confusing topic for a lot of new developers. In fact, it's a question I happened to get wrong in a job interview and as a result I've put some effort into studying and understanding it (don't want to make the same mistake twice). This blog post will go through the 4 principles of 'this' and how each one is applied in JavaScript.

Let's start by talking about what 'this' means in english. If I were to tell you "Hey! Look at this" - what is this? You really have no idea what 'this' refers to unless I give you some context. If I pick up an object and point to it and say "look at this" you will quickly be able to understand that 'this' refers to the object in which I am pointing to. It's the same in JavaScript. If we give this no context at all it will return the window to us, the global object in node and undefined in strict mode. This brings us to our first principle of 'this'

1. Window Binding

Window binding is not something we aim to use. It's what happens when we do not give context for the 'this' keyword. If we don't tell JavaScript what 'this' is it will return the window to us, the global object in node or undefined in strict mode.
Here's an example:

function sound(){
console.log(this.bark);
}

sound() // we will get back the window in the console
Enter fullscreen mode Exit fullscreen mode

This brings us to our next principle:

2. Implicit binding

Implicit binding is probably the most common principle of 'this', it apply to objects with methods and it says when the function is invoked, look to the left of the dot. That's what 'this' refers to.
Let's see an example:

const ada = {
name: 'Ada',
breed: 'Bali dog',
bark: 'woof woof'
sound: function(){
    console.log(this.bark);
  }
}

ada.sound(); // invoking the function. This bark refers to ada's bark because ada is the left of the dot when the function is invoked

Enter fullscreen mode Exit fullscreen mode

A good way to remember implicit binding is that it's IMPLIED that whatever is to the left of the dot when the function is invoked that's what 'this' will refer to.

3. Explicit Binding

With explicit binding we explicitly pass in as an argument what we want 'this' to refer to. We do that using .call(), .apply(), or .bind(). There are some differences with how we use these.
.call() - will immediately invoke the function, with .call you pass in the arguments 1 by 1
.apply() - will immediately invoke the function, with .apply you would pass in the arguments as an array
.bind() - you will pass in your arguments 1 by 1 but it does not immediately invoke the function, instead it returns a brand new function that can be invoked later.
Let's see an example of .call:

function sound(){
console.log(this.bark);
}

const ada = {
name: 'Ada',
breed: 'Bali Dog',
bark: 'woof woof'
}

sound.call(ada); // invoking the function here and binding this bark to Ada's bark - we will get back woof woof in the console.
Enter fullscreen mode Exit fullscreen mode

Let's see an example of .bind:

function sound(){
console.log(this.bark);
}

const ada = {
name: 'Ada',
breed: 'Bali Dog',
bark: 'woof woof'
}

const goodDog = sound.bind(ada); // creating a new function called goodDog that can be invoked later

goodDog(); // we will get back bark bark in the console
Enter fullscreen mode Exit fullscreen mode

4. New Binding

New binding is used with constructor functions. It says that when a function is invoked as a constructor function using the 'new' keyword 'this' points to the newly created object. At this point you may be asking "what is a constructor function?" Great question! A constructor function constructs other objects, that's it's whole life's purpose. Some things you may notice about a constructor function is that it has a capitalized function name, there is an assignment of the 'this' keyword and it may be missing a return statement.
Let's see an Example:

function Pet(name, breed, bark){
this.name = name;
this.breed = breed;
this.bark = bark;
}
Pet.prototype.sound = function(){ //here we are using the prototype keyword to abstract out the sound method so that we can pass it across newly created objects without if affecting memory
  console.log(this.bark);
}

const ada = new Pet('Ada', 'Bali Dog', 'woof woof'); // creating my new object - this.name will be 'Ada' this.breed will be 'Bali Dog' and this.bark will be 'woof woof' - 'this' is pointing to my newly created object which is ada. 

ada.sound()// will log 'woof woof'
Enter fullscreen mode Exit fullscreen mode

One final call out, do not use arrow functions inside of object methods. Arrow functions don't bind this 'this' keyword, they pass it through. This is not a bug, it's feature of arrow functions. For more on that Wes Bos wrote a really great post called Arrow Function No No's linked here

I hope this was helpful for you! Let me know in the comments <3

Top comments (6)

Collapse
 
beitist profile image
Sebastian Stüwe

Hi Brit,

can you explain the implicit binding a bit more? I had situations where this wouldn't function, but I couldn't find out why (not); in particular with stacked calls.

Apart from that - very helpful! :-)

All the best,
Sebastian.

Collapse
 
brityhemming profile image
Brit Hemming

Hey Sebastian, Thanks for your comment. Could you give me an example of where implicit binding didn't work for you?

Collapse
 
beitist profile image
Sebastian Stüwe

Hi Brit,

sorry for the late response - I couldn't exactly remember where it happened, but now I found the problem:

Assume the following micro-example:

START_NODE = document.getElementById('start');

class Major {
  constructor() {
    this.minor = new Minor();
    this.handler = '';
  }

  initHandler() {
    this.handler = new Handler();
  }

  listenToHandler(parameter) {
    this.minor.someValue = parameter;
  }

}

class Minor {
  constructor() {
    this.someValue = '';
  }
}

class Handler {
  constructor() {
    this.button = document.createElement('button');
    this.button.innerText = 'Click!';
    this.button.addEventListener('click', major.listenToHandler.bind(3));
    START_NODE.appendChild(this.button);
  }
}

let major = new Major();
major.initHandler();
Enter fullscreen mode Exit fullscreen mode

Here's a possible corresponding html-file:

<!DOCTYPE html>
<html>
  <head>
    <script src='bindproblem.js' defer></script>
  </head>
  <body>
    <div id='start'></div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

You'll notice that JS throws a TypeError which must have something to do with the scope, I presume. I've usually found workarounds, but I do not like them. I'd like to really understand the problem.

Thanks!
Sebastian.

Thread Thread
 
brityhemming profile image
Brit Hemming

This is likely a scope issue. A couple of things to know about classes:

  • classes are not hoisted - that means if we are putting our information above a class we have referenced it will not work
  • they use strict - it prevents window binding, forces us to write cleaner code with errors if we do not
  • Methods are a special syntax
  • A constructor function is visible

In your case point 1 is the issue - classes are not hoisted. You've created Handler below where you are invoking it.

Here's a codepen where I explain classes a little deeper. Hopefully that's helpful :)

codepen.io/BritHemming/pen/WNvYJMr

Collapse
 
mahmoudessam profile image
Mahmoud EL-kariouny

Thanks for your effort ;)

Collapse
 
zer0 profile image
zer0

this. Article is :

cannot read Article of null 
Enter fullscreen mode Exit fullscreen mode

/s 😁