this

ES6 Classes

Key Questions

  • What is `this`?
  • What is "prototypal inheritance"?
  • How does `this` relate to inheritance?
  • What are "classes" in JavaScript?
  • How does `this` come into play in classes?

'this'

possibly the most misunderstood keyword in javascript

No accepted answers 😢

simple definition

this equals the context of the nearest parent object from which this was evaluated at execution time

simpler definition

In JavaScript, "this" refers to the object invoking the function that is being executed

So, lets see some

"toy examples"

to test that definition

//in global scope

console.log(this);
function foo(){
    console.log(this);
}

foo();
const foo = {
    bar: function(){ 
        console.log(this) 
    }
};

foo.bar();
const clickHandler = function(evt) {
    console.log(this);
};

document
    .querySelector('#element')
    .addEventListener('click', clickHandler);
const that = this;

const clickHandler = function(evt) {
    console.log(that);
};

document
    .querySelector('#element')
    .addEventListener('click', clickHandler);
let clickHandler = function(evt) {
    console.log(that);
};

clickHandler = clickHandler.bind(this)

document
    .querySelector('#element')
    .addEventListener('click', clickHandler);

x

const person = {
    greet: function(){
        console.log(this);
    }
}

const mygreet = person.greet;
mygreet();
function baz(){
  console.log(this);
}

const foo = {
  bar: function(){
    baz();
  }
}

foo.bar();
const clickHandler = (evt) => {
    console.log(that); // window
};

document
    .querySelector('#element')
    .addEventListener('click', clickHandler);

Arrow Function

this differences

An arrow function does not create its own this context, so this has its original meaning from the enclosing context.

-https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

What is prototypal inheritance?

Let's answer that question by comparing and contrasting Classical Inheritance

Classical

Classes define "patterns" or "templates" which are not instanciated

Prototypal

No classes, only actual objects and instanciated things

Different types of inheritance: single, multi-level, multiple, hybrid

Children only inherit from their prototype object

Both are a system for sharing/reusing functionality/behavior

Ok, so how does it actually work?

There is no such thing as a class (nothing abstract)

There are several global objects in every JavaScript environment. They are actual objects.

 

  • Object
  • Function
  • Boolean
  • Symbol
  • Error
  • ...
  •  

These are the fundamental, basic objects upon which all other objects are based. This includes objects that represent general objects, functions, and errors.

The Object object has methods which can be called

 

Object.freeze()

Object.create()

Object.keys()

 

The Object object also has prototype methods which other objects inherit

 

Object.prototype.toString()

Object.prototype.valueOf()

Every object contains a prototype property, which is a pointer (link) to the actual object that it "inherits" from

When a method not defined on an object is called, the prototype "chain" is crawled until the method is found, or not.

const person = { name: 'Bob' };

person.toString() // [object Object]

person

 - name

 - prototype

     - __proto__  

Object

 - keys

 - ...

 - prototype

    - __proto__

    - ...

     - toString 

Cat

 - name

 - prototype

    - __proto__

Object

 - keys

 - prototype

   - toString

   - __proto__

Animal

- Genus

- prototype

   - speak

   -__proto__

Cat

 - name

 - prototype

    - __proto__

Object

 - keys

 - prototype

   - toString

   - __proto__

Animal

- Genus

- prototype

   - speak

   - toString

   -__proto__

Overriding prototype methods

How does this relate to prototypal inheritance

The new keyword executes the function in a new context. This equals a new object

const Person = function({name, age}) {
    this.name = name;
    this.age = age;
    this.greet = function() { return "hey"; }
}

const Bob = new Person({name: 'Bob', age: 35});

This pattern is called a "constructor function"

const Person = function({name, age}) {
    this.name = name;
    this.age = age;
    this.greet = function(){ return "hey"; }
}

const Bob = new Person({name: 'Bob', age: 35});

After declaration, properties can be added the to "constructor" function

const Person = function({name, age}) {
    this.name = name;
    this.age = age;
}

Person.prototype.doWork = function(){ }


const Bob = new Person({name: 'Bob', age: 35});
Bob.doWork()
const Person = function({name, age}) {
    this.name = name;
    this.age = age;
}

Person.prototype.doWork = function(){ }


const Bob = new Person({name: 'Bob', age: 35});
Bob.doWork()

Bob

 - name

 - age

 - prototype

   - __proto__

Person

- prototype

  - doWork

Enter ES6 Classes

Managing inheritance over multiple objects and prototype chains can be complex and difficult

ES6 classes make creating objects with prototypal inheritance easier

ES6 classes are "syntactic sugar" to make constructor functions look more approachable

JavaScript classes introduced in ECMAScript 2015 are syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax is not introducing a new object-oriented inheritance model to JavaScript. JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.

-https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

ES6 classes are not classical OOP classes

class Person{
    constructor({name, age}){
        this.name = name;
        this.age = age;
    }
}

const Bob = new Person({name: "Bob", age: 35});
const Person = function({name, age}) {
    this.name = name;
    this.age = age;
}

const Bob = new Person({name: "Bob", age: 35});

Equivalent

class Person{
    constructor({name, age}){
        this.name = name;
        this.age = age;
    }
}

const Bob = new Person({name: "Bob", age: 35});

class keyword

opening and closing curly brace

constructor method

Must use new keyword

class Person{
    constructor({name, age}){
        this.name = name;
        this.age = age;
    }
}

const Bob = new Person({name: "Bob", age: 35});

extends

 

classes can use the extends keyword to create a prototypal inheritance link

class Animal{
    constructor({name}){
        this.name = name;
    }
}

class Cat extends Animal{
    constructor({name, color}){
        super({name});
        this.color = color;
    }
}

Animal extends Cat & passes Cat's constructor some properties

class Animal{
    constructor({name}){
        this.name = name;
    }
}

class Cat extends Animal{
    constructor({name, color}){
        super({name});
        this.color = color;
    }
}

Cat

 - color

 - prototype

    - __proto__

Object

 - ...

 - prototype

   - __proto__

   - ...

Animal

- name

- prototype

   -__proto__

class Cat extends Animal{
    constructor({name, color}){
        super({name});
        this.color = color;
    }

    speak(){
        return "🐱 Meow."
    }
}

const Spot = new Cat({name: "Spot", color: "#d89400"});
Spot.speak(); //"🐱 Meow."

Classes can contain "methods". In reality, a method is just a function attached to the object prototype 

class Cat extends Animal{
    constructor({name, color}){
        super({name});
        this.color = color;
    }

    speak(){
        return `${this.name} says "Meow" 🐱`;
    }
}

const Spot = new Cat({name: "Spot", color: "#d89400"});
Spot.speak(); // 'Spot says "Meow" 🐱';

Since we are using this in a function, it refers to the object which called it -- Spot in this case. this.name is resolved by following the prototype chain up to Animal

class ButtonWidget{

  constructor({alertValue}){
    this.alertValue=alertValue;
  }

  clickHandler(){
    console.log(this.alertValue);
  }

  render(){
    const btn = document.createElement('button');
    btn.textContent = "Click Me!";
    btn.addEventListener('click', this.clickHandler);

    return btn;
  }
}

Spot the bug:

class ButtonWidget{

  constructor({alertValue}){
    this.alertValue=alertValue;
    this.clickHandler = this.clickHandler.bind(this);
  }

  clickHandler(){
    console.log(this.alertValue);
  }

  render(){
    const btn = document.createElement('button');
    btn.textContent = "Click Me!";
    btn.addEventListener('click', this.clickHandler);

    return btn;
  }
}

Now clickHandler works as expected

Applying stuff we learned to React Components






class SimpleButton extends React.Component{
    constructor(props){
        super(props);
        this.clickHandler = this.clickHandler.bind(this)
    }
    
    clickHandler(evt){
        // "this" is now properly bound 
        // to the context of the class
    }

    render(){
        return(
            <div>
                <button onClick={this.clickHandler}> Click Me! </button>
            </div>
        )
    }
}
class SimpleButton extends React.Component{
    constructor(props){
        super(props);
    }
    
    clickHandler = (evt) => {
        // using an arrow function, `this` doesn't get 
        // assigned to it's callers context (button)
    }

    render(){
        return(
            <div>
                <button onClick={this.clickHandler}> Click Me! </button>
            </div>
        )
    }
}

Arrow functions as class properties are a proposal for ES7 that is likely to be adopted. This syntax is supported in the React Starter Kit

this is great.

 

Prototypal inheritance is great.

 

React is great.

You're great, too :)

THE END

this, prototypes, es6 classes, & react

By Michael Jasper

this, prototypes, es6 classes, & react

  • 838