Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript classes and 'this'

Tags:

javascript

class Aa {
    methodA() {
        console.log('welcome to method a')
    }
    methodB() {
        console.log('welcome to method b')
        //   methodA() // Fails!! Uncaught ReferenceError: methodA is not defined
        this.methodA() // Works because of this.
    }
}

let myclass = new Aa()
myclass.methodB()

What is the best way to concisely explain why you need to use 'this' when calling another method of the class that you are already in?

Intuition might say, well if JS knows that I'm using a method of a class, then it knows about that class...because I'm using it's method.....so why can't it find another method of that same class without me telling it...yeah 'this' same place!

By way of contrast, functions can do it with no problem:

function a() {
    console.log('welcome to function a')
}
function b() {
    console.log('welcome to function b')
    a() // works without any sort of 'scope help'
}
b()

I want to be able to explain it without getting people distracted with needing to know the deepest reasons for it. If possible, ha!

Part of me just wants to say, "That's how JS classes work. You gotta 'this'-ify things."

like image 208
Ivan Town Avatar asked Dec 13 '22 15:12

Ivan Town


2 Answers

In order to understand why we need to explicitly reference this in Javascript classes, we first need to understand what the this reference points to in ordinary functions.

This

In Javascript the this keyword is always a reference to the object that called the function.
Consider the following:

const obj1 = {
  foo() { console.log(this); }
}
obj1.foo(); // "this" will reference "obj1"

Nothing strange here, this is a reference to the object where it was defined obj1.


Now think about what would happen if we took the function foo and "removed" it from the object. Since this is a reference to the object that called the function, what should this be if the function doesn't belong to an object?

const obj1 = {
  foo() { console.log(this); }
}
const foo = obj1.foo;
foo(); // "this" will reference "window"

This is were things start to become strange, this is actually a reference to the global object. This is because everything in Javascript is an object, even the root level of a file. In the browser this global object is called the window object.


Now consider what happens if we reattache the method to another object?

const obj1 = {
  foo() { console.log(this); }
}
const foo = obj1.foo;
const obj2 = {};
obj2.foo = foo;
obj2.foo(); // "this" will reference "obj2"

The same rules apply here, since the function now belongs to an object again the this reference points to obj2.

Classes

Fist of all Javascript doesn't actually have classes. A js class in just a different way of writing a prototype.

Lets start by writing a simple class.

class Foo {
  constructor() { console.log("I'm a Class constructor") }
  log() { console.log("I'm a Class method") }
}

const obj1 = new Foo(); // I'm a Class constructor
obj1.log(); // I'm a Class method 

Now lets write the same "class" as a prototype.

function Bar() {
  console.log("I'm a Prototype constructor")
}
Bar.prototype = { 
  log() {
    console.log("I'm a Prototype method")
  }
}

const obj2 = new Bar(); // I'm a Prototype constructor
obj2.log(); // I'm a Prototype method

These two ways of writing inheritance (classes & prototypes) are the same thing in Javascript.

So as we can more clearly see in the prototype implementation the "class methods" are actually just an object assigned to the prototype of a function.


Now that we know about this and about classes / prototypes we can see that a class method is actually just a function on an object and that this refers to the the object that called the function. So if we want to reference another function on the same object we should do so by referencing it through the this reference.

class Aa {
    methodA() {
        console.log('welcome to method a')
    }
    methodB() {
        console.log('welcome to method b')
        this.methodA()
    }
}

const myclass = new Aa()
myclass.methodB()
like image 67
Olian04 Avatar answered Dec 22 '22 09:12

Olian04


It is confusing mostly because Javascript has a long history of trying to fit the square peg of prototypes into the round hole of classes. A lot of things that make sense in traditional object-oriented languages only lead to confusion in javascript.

Your (very reasonable) intuition goes off the rails here:

Intuition might say, well if JS knows that I'm using a method of a class, then it knows about that class...because I'm using it's method

methodA() and methodA() are just functions. They don't know anything about where they were defined. What they know is how they were called. this is the key to tying how the function is called to the rest of the class. Consider this example:

class Aa {
  methodA() {console.log('welcome to method a')}
  methodB() {
    console.log('welcome to method b')  
    this.methodA() // Works because of this.
  }
}

let obj = {
  methodA() { console.log("welcome to imposter A")}
}

let myclass = new Aa()

obj.methodB = myclass.methodB // take a reference to methodB and add it a different obj

console.log("Same?",obj.methodB === myclass.methodB) 
// identical function but it has no idea
// it "belongs" to myclass. Called from a different context
// it acts like a normal function (because it is)
obj.methodB() 

The function obj.methodB doesn't know it belongs to class Aa. The only thing the relates it to the class is the way it's called and the way it's called determines the value of this. So this (and the prototype chain) is the glue that holds the whole scheme together.

like image 41
Mark Avatar answered Dec 22 '22 11:12

Mark