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);
}
}
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'
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
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);
}
}
This now works.
let instance = new SomeClass();
let funcVariable = instance.someMethod;
funcVariable(); // logs 'someValue'
const { someMethod } = instance; // destructuring also works!
someMethod(); // logs 'someValue'
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();
The property someMethod
is defined on the constructor function’s prototype
.
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();
The property someMethod
is not defined on the constructor function’s prototype
.
Instead, it is defined on the instance
.
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
}
}
Not endorsing one approach or the other, just describing the difference. Oh, and guess which browser is completely irrelevant to this whole discussion.
Top comments (1)
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.