Classes and "this" keyword explained on TypeScript examples

Publish date:

At the beginning of adventure with programming, many consider the concept of classes as problematic, that's why we'll take a closer look at classes with simple examples in TypeScript. As our role models to go with them through the topic, I've chosen Kid and Mom because I think this is the most universal example familiar to all people.

How to build a kid?

At first, we will take a look at how kid should be represented as a class, and what is class in general. You can think about class like a set of instructions of how to build something and how later interact with the thing we're going to build. Consider the below example in pure English:

Recipe for kid:
1. create body
2. attach head at the top
3. attach two legs at the bottom
4. attach two arms on each side

Methods of interaction with kid:
1. you can tell it to move it's arms
2. ...

Simple, don't you think? The same instructions we can translate into code, and the best representation for objects, like our kid, car, mug or lamp is to use class . That's why classes are present in most of the modern programming languages. Small example, I'll translate the above instruction into classes, and we'll try to build Kid on top of them.

Instruction telling us that we need to have two legs, two arms, one head and one body. As I told you before, classes are good tool when it comes to represent objects, that's why all mentioned body parts are great candidates for new classes!

class Head {

}

class Leg {

}

class Arm {

}

class Body {

}

Okay, we have classes. But you may wonder why did I create only one class Leg while instruction clearly states that kid needs to have two legs. Very good observation! While class is a set of instructions, we expect it'll be able to produce something. Like cars, Car factory has a production line, this production line was build once, and now it can produce as many cars as we want. The same applies to classes, but class produces something called instances instead of cars. Instance is an object produced by a class after calling it with special keyword new. Let's put it all together in example:

// our factory
class Car {

}

// and we have production line, we creating new car instaces. We have only one class Car but we can create milions of cars
const myCar1 = new Car();
const myCar2 = new Car();
const myCar3 = new Car();
// ...

The same applies to our previous example, we only need one class Leg and one class Arm because we can create as many legs and arms as we want to. What's next? I see one more thing here. Our kid needs a body, but should it know about how it's body is made? Nah. It's too young, will be scared, we need to hide this knowledge from it. That's why we will give to our child already composed body, ready to use! How to do it you may ask, here it is:


class Head {

}

class Leg {

}

class Arm {

}

class Body {
    constructor(
        public head: Head,
        public rightLeg: Leg,
        public leftLeg: Leg,
        public rightArm: Arm,
        public leftArm: Arm,
    ) {}
}

What's going on in the above example? Class body is an instruction of how to put together body parts to build a functional body, which we'll later give to our kid. We also have something new named constructor, so it's time for a new chapter.

Who constructs kid?

The constructor is a special function in class, it is called when we want to create a new leg, hand or body. And name is actually very accurate because it constructs our object. It may assign some properties like: public head: Head - this notation in TypeScript informs our class:

when you're creating a new Body instance someone needs to pass to your constructor a head, and head needs to be an instance of class Head, if it is then save Head in a variable under name head, same for legs and arms.

// the constructor() method will be called here
// where the "new" keyword stands before class name
const body = new Body();

Ooops, but didn't we forget to pass body parts to the constructor as we declared in definition? Below is the corrected version.

const head = new Head();
const rightLeg = new Leg();
const leftLeg = new Leg();
const rightArm = new Arm();
const leftArm = new Arm();

const body = new Body(
    head,
    rightLeg,
    leftLeg,
    rightArm,
    leftArm
);

That's it! Now in const body we keep our body instance, and body instance keeps information about body parts we passed to its constructor. If you do something like this:

console.log(body.arm);

console will print arm instance assigned to that body!

We are on fire! But we forgot about a very important thing. Our kid needs to move somehow, do you remember about methods of interaction from recipe? Yep, we need to add methods to our class, so like you probably think, it's time for the next chapter.

First moves

To add Kid ability to move we need to create a function which will perform some actions to move arms. We can do it like this:

function move(arm: Arm) {
    // move arm
}

But like I said before, classes are a good tool for organizing code around some specific objects, like Kid. Class is a kind of contract which allows programmers to organize and split their code around smaller data structures, and on the other hand assures the computer that when it will call a method on this structure, the method will know how to deal with it.

Method? Yes. Methods are functions interacting with instances created by the class. They are just functions bound to class. Our recipe doesn't specify how exactly the kid should move, that's why we'll add just a method named move(). Recipe specifies that kid needs to move only it's arms, that's why class Arm must be modified:

class Arm {
    move() {
        // here goes the body of method, let's assume that it is a very
        // sophisticated code which allows arm to move ;)
        console.log('Move arm, move!')
    }
}

Great! Now we know that Arm can move, but our body has no idea about it! We need to modify class Body as well, it must move both it's arms, left and right at once.


class Body {
    constructor(
        public head: Head,
        public rightLeg: Leg,
        public leftLeg: Leg,
        public rightArm: Arm,
        public leftArm: Arm,
    ) {}

    moveArms() {
        console.log(`Watch out! I'm gonna move my arms!`)
        this.rightArm.move();
        this.leftArm.move();
    }
}

Now Body class gained ability to move its arms by calling method moveArms on body instance. You may wonder how class knows that I wanted it to move arms on a specific body, since one class has many instances. Here into action comes another magic word - this. We'll talk about this more ... now!

Who's your mom?

Can you think about your mom for a moment? Great! Now imagine yourself as 5yo, let's say standing at the school academy on the occasion of Mother's Day, teacher gives sign and all the kids are looking for their mothers to give them flowers. But wait... there are a lot of children there, and lots of mothers, teacher said "find your mom's", why all the kids didn't came to one person when teacher said to all of them the same word "mom"? Because each kid has its own context. In that context magic word mom is saved, if I ask you "where is your mom?", you will point me person on the left, but if I'll ask your friend the same question he'll point other person on the right, nevertheless I asked you both the same question. This is it, in the programming, this is a magic keyword which gives us access to a memory linked to specific class instance. You don't need to call my mom by name and surname, it's enough to use just the word mom and I know which person we are talking about in my context, or yours.

So, how does it work in case of programming and classes? Checkout the below example

class Kid {
    // kid needs to know it's mother
    constructor(public readonly mom: Mom) {}

    // we know that each kid keeps information about mom.
    // we can ask all kids the same question.
    // Method has access to class instance by our magic keyword "this",
    // which means it can ask each kid created by "new Kid()" same question
    // and you can be sure that each kid has the right answer
    whatsYourMomName() {
        return `My mom name is ${this.mom.name}`;
    }
}

class Mom {
    // string is a special type reserved for text, it is builtin into TypeScript
    constructor(public readonly name: string) {}
}

// Create mothers for kids
const eliotsMom = new Mom('Julia');
const barbarasMom = new Mom('Kirsten');

// those are class instances. Every instance has it's own context, and mother is
// saved in this context
const eliot = new Kid(eliotsMom);
const barbara = new Kid(barbarasMom);

// now we can ask each kid question about it's mom and they will
// respond with right answers, because they all keep information in their
// personal contexts
console.log(eliot.whoIsYourMom()); // My mom is Julia
console.log(barbara.whoIsYourMom()); // My mom is Kirsten

Hurray! We just recreated the same behavior, we can ask each kid about their mom's name, and each will give us different answers even though the question stays the same for all of them.

First words

Now, when we know what this keyword is, and we know methods, we can talk a bit more about memory optimization class does for us. As we said before, class is just a set of instructions of how to create and interact with an instance, and this is also the case when it comes to computer memory. When you create new instance of Kid computer reserves some memory in RAM needed to store this instance, but it applies only to class properties like those passed in constructor, because they are part of the instance class produces, not a class itself. Each instance can have its own unique values saved in properties, class knows exactly how the object created by itself looks like, which gives class the ability to do some savings by... storing methods only once in memory.

Yes, methods are not copied when you create new class instance, they are saved in memory once when class is defined and later class do all the magic for us. You can create millions of kids, but methods will stay the same, when you call a method on class instance, under the hood it calls a function defined once on class and magically pass instance hidden under the keyword this.

Thanks to this mechanism we always know, that under this in method we have current instance, we can reuse methods across all the instances created by the same class which results in huge memory savings.

Example:

class Kid {
        constructor(public name: string) {

    }

    whatsYourName() {
        console.log(`I'm ${this.name}`);
    }
}

const eliot = new Kid('Eliot');
const barbara = new Kid('Barbara');

eliot.whatsYourName(); // I'm Eliot
barbara.whatsYourName(); // I'm Barbara

You can think about the above example in this way: each instance of class has special links to methods defined on class, when I call a method on instance

eliot.whatsYourName();

it does for me something like this

class Kid {
    whatsYourName() {
        // I know you called me from instance "eliot" so
        // I will make it easier for you and assign "eliot" instance
        // under my special variable named "this" so you dont't have
        // to do it manually
        this = eliot;
        console.log(`I'm ${this.name}`); // I'm Eliot
    }
}

We know what this keyword is, and how it works, now we can go further with the initial example.

I'm moving!

Let's erase kid class, and define body property in constructor


class Kid {
    constructor(
        public body: Body
    ) {}

    moveArms() {
        this.body.moveArms();
    }
}

Success! We've translated the whole recipe written in pure English into the code which is easy to understand by both, humans and computers. Amazing job! Finally, we can create kids


const elliotsBody = new Body(
    new Head(),
    new Leg(),
    new Leg(),
    new Arm(),
    new Arm(),
);

const barbarasBody = new Body(
    new Head(),
    new Leg(),
    new Leg(),
    new Arm(),
    new Arm(),
);

const eliot = new Kid(elliotsBody);
const barbara = new Kid(barbarasBody);

eliot.moveArms();
barbara.moveArms();

What we learned in this lesson?

  1. Classes are great when it comes to representing objects or some specific problems in code
  2. The class is an instruction of how to construct an instance and later how to interact with it. Class !== instance. Class instance is just a product of calling new ClassName();
  3. Constructor is a special function which runs as the first thing when we use new keyword on class, it creates instance for us, assigns values to properties etc.
  4. You can interact with a class instance by calling methods on it. Methods are just functions defined on class, but their superpower is a special keyword this which gives them access to the current instance you called the method on. Methods are created once, and they never change regardless of how many class instances you're creating.

The end

That's it, we talked a bit about classes in TypeScript and some very basic concepts of them. I hope this material was helpful and if you liked it please share on Twitter, Facebook, LinkedIn or leave a comment, if it wasn't, I'm open to suggestions and feedback ☺️