JavaScript the Fun Part


effective-javascript-2

type-of-this-binding


A quick review of types of this binding in JavaScript.

Implicit this binding

This is the default behavior of a function in JavaScript.

When we declare a function and just use it as a function there is no this binding since it happens when we use a function as a method for an object.

In this case that function becomes a method of that object.

Then if we call the function using the object, the this - if it has been used in the function - is bound to that object.

// simple object
const log = console.log;

const object = {
    x: 10,
    getX: function(){
        return this.x;
    }
};

// here this refers to "object" the owner of getX() function
log( object.getX() ); // 10

Problem of this dynamic this binding is that if we pass getX() function around then we do no have the proper this to use.

// simple example based on above example
// on the global scope
const getX = object.getX;

// now we have getX() at global scope
// lets run it
log( getX() );        // undefined

Why? Simple this.getX at global scope does not equal to object.getX. These are run in two different context.

The this.x with object.getX() is run in the context of object Object.

But getX() with const getX = object.getX is run in the context of global scope which is window Object.

And we get undefined since at global object there is no x.

We can simply prove this by adding this.x = "ha ha ha" at global scope like so:

// add x to this at global scope
this.x = "ha ha ha";

// new calling getX()
log( getX() );         // "ha ha ha"

Explicit this binding

One solution to avoid or fix implicit this binding is by explicitly add it to a function we want to call it in other contexts.

For our object example we simply can tell the getX() to use a reference for its this.

// telling getX() to use which object as a reference for its this binding
// we tell the getX() the owner for "this" is object not window, etc
getX.bind( object )(); // 10

The second parentheses is because the bind() function returns a new function and do not modify the original one.

Forward this binding

Imagine you want to use a higher order function like Array.map() and you should pass an anonymous function to do the task for you.

// simple example
const people = {
    list: [ "A", "B", "C" ],
    greeting: "hello "
};
people.list.map(function( person ){
    console.log( this.greeting + person ); // wrong this!
});


// output
undefinedA
undefinedB
undefinedC

The problem here is that this.greeting is in a wrong context.

Because we use an anonymous function and it has implicit this binding, then this in this anonymous function does not have .greeting property and result is undefined.

The solution is to tell the anonymous function to pick up the proper "this".

The first way we can fix this is by forwarding owner of this.greeting to the map function.

// simple example
people.list.map(function( person ) {
    return this.greeting + person; // right this!
}, people);

// output
Array(3) [ "hello A", "hello B", "hello C" ]

The second solution is by explicitly binding the owner

// simple example
people.list.map(function( person ) {
    return this.greeting + person; // right this!
}.bind( people ));

// output
Array(3) [ "hello A", "hello B", "hello C" ]

Super class this binding

It is simply for using a Parent class property and assign them to sub-class.

In ES5 we use e.g Parent.call( this, x, y ).

In ES6 we use e.g super( this ).

In React we use e.g super( props ).

// simple object
const log = console.log;

/*********** ES5 ***********/
function A( x, y ){
    this.x = x;
    this.y = y;
}

const xy = new A( 1, 2 );
log( xy );      // Object { x: 1, y: 2 }

function B( x, y, z ){
    // it mean on B assign x, and y using A
    A.call( this, x, y );
    this.z = z;
}

const xyz = new B( 4, 5, 6 );
log( xyz );     // Object { x: 4, y: 5, z: 6 }

/*********** ES6 ***********/

class C {
    constructor( x, y ){
        this.x = x;
        this.y = y;
    }
}

const xy_c = new C( 1, 2 );
log( xy_c );    // Object { x: 1, y: 2 }

class D extends C {
    constructor( x, y, z ){
        // on D assign x, and y using C
        super( x, y );
        this.z = z;
    }
}

const xyz_d = new D( 4, 5, 6 );
log( xyz_d );   // Object { x: 4, y: 5, z: 6 }

// output
Object { x: 1, y: 2 }
Object { x: 4, y: 5, z: 6 }
Object { x: 1, y: 2 }
Object { x: 4, y: 5, z: 6 }

Fixed this binding

We know that calling Function.bind( object ) returns a new function that "this" has been bound there.

If we do this, we get a new function that "this" in that function has been fixed and passing it around in any contexts works fine for us.

For example in our first example

// simple object
// simple object
const log = console.log;

const object = {
    x: 10,
    getX: function(){
        return this.x;
    }
};

// here this refers to "object" the owner of getX() function
log( object.getX() ); // 10

// "this" has been fixed to object, it real owner
const getX = object.getX.bind( object );

// does not matter where it is run
// lets run it
log( getX() );        // 10

And I am sure you have seen such a pattern in React.

// fixed this binding in react
this.onClick = this.onClick.bind( this );

It simply means hey "onClick()" function, wherever you is run, you will not lose your this owner, it has been bound for you :).


Update: Sat Sep 07 2019 12:50:43 GMT+0430 (Iran Daylight Time)