DEV Community

Suhan Wijaya
Suhan Wijaya

Posted on • Originally published at Medium

Traditional versus Arrow functions in JavaScript Classes

The two states of every programmer
Source: Programmer Humor


This is a spiritual sequel to this article.

Create a class with a method using Traditional function like so. Let’s call this Approach A.

// APPROACH A

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

    someMethod() { // Traditional function
        console.log(this.someProp);
    }
}
Enter fullscreen mode Exit fullscreen mode

Create an instance of that class. When invoking the method on the instance, this refers to the instance. So far, it’s behaving as expected.

let instance = new SomeClass();

instance.someMethod(); // logs 'someValue'
Enter fullscreen mode Exit fullscreen mode

But, as soon as we assign the method to a variable and call that function variable, the method loses its context, and you get Uncaught TypeError: Cannot read property ‘someProp’ of undefined.

let instance = new SomeClass();

let funcVariable = instance.someMethod;
funcVariable(); // logs error
Enter fullscreen mode Exit fullscreen mode

OK.

Now, let’s create the class with a method using Arrow function like so. Let’s call this Approach B.

// APPROACH B

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

    someMethod = () => { // Arrow function
        console.log(this.someProp);
    }
}
Enter fullscreen mode Exit fullscreen mode

This now works.

let instance = new SomeClass();

let funcVariable = instance.someMethod;
funcVariable(); // logs 'someValue'

const { someMethod } = instance; // destructuring also works!
someMethod(); // logs 'someValue'
Enter fullscreen mode Exit fullscreen mode

Why, JavaScript, why?

As per MDN docs, “the class keyword is introduced in ES2015, but is syntactical sugar, JavaScript remains prototype-based.” So if we were to write in pre-ES6 syntax, Approach A looks like this.

// Equivalent to APPROACH A

'use strict';

var SomeClass = function() {
    this.someProp = 'someValue';
}

SomeClass.prototype.someMethod = function() {
    console.log(this.someProp);
}

var instance = new SomeClass();
Enter fullscreen mode Exit fullscreen mode

The property someMethod is defined on the constructor function’s prototype.
Chrome Dev Console


But not on the instance.
Chrome Dev Console


You can access instance.someMethod through prototypal inheritance.

But when you assign instance.someMethod to another variable, the function variable loses its context.

Further, since “code within the class body's syntactic boundary is always executed in strict mode”, this will be undefined instead of defaulting to window or global.

OK.

Now, Approach B looks like this in pre-ES6:

// Equivalent to APPROACH B

'use strict';

var SomeClass = function() {
    this.someProp = 'someValue';

    var _that = this;

    this.someMethod = function() {
        console.log(_that.someProp);
    }
}

var instance = new SomeClass();
Enter fullscreen mode Exit fullscreen mode

The property someMethod is not defined on the constructor function’s prototype.
Chrome Dev Console


Instead, it is defined on the instance.
Chrome Dev Console


Further, an Arrow function is bound to its surrounding lexical context by default (where it physically sits in the code), which seems equivalent to a Traditional function having access to an outer function variable that points to this (i.e., closure).

Hence, even when you assign instance.someMethod to another variable, the function variable remains bound to the instance context.

Note: I’m not 100% sure about the actual ‘under the hood’ mechanism by which Arrow functions derive this, so feel free to comment if you do know.

In any case, I went down this rabbit hole because I’ve been using Arrow functions for writing methods in classical React components, instead of binding Traditional functions (i.e., this.someMethod.bind(this)) in the constructor or when passing it down as prop.

import React from 'react';

class SomeComponent extends React.Component {
    constructor(props) {
        super(props);

        this.state = { /* some state */ };
    }

    someMethod = () => { // Arrow function
        // will have access to `this.state`
    }

    render() {
        // may attach `this.someMethod` as an event handler or 
        // pass it down as a prop to child components
    }
}
Enter fullscreen mode Exit fullscreen mode

Not endorsing one approach or the other, just describing the difference. Oh, and guess which browser is completely irrelevant to this whole discussion.


📫 Hit me up on LinkedIn or Twitter!

Top comments (1)

Collapse
 
taufik_nurrohman profile image
Taufik Nurrohman

FYI, the non-arrow function (the prototype style) is preferred because you don’t need to create those methods again and again on every instance, so it will be less in case of memory usage.

Reference: stackoverflow.com/q/310870/1163000

But, yeah. Just don’t over-optimize it.