14

I was wondering - what is the difference between class method, class property which is a function and class property which is an arrow function? Does the 'this' keyword behave differently in the different variants of the method?

class Greeter {

  constructor() {
    this.greet();
    this.greet2();
    this.greet3();
  }

  greet() {
    console.log('greet1', this);
  }

  greet2 = () => {
    console.log('greet2', this);
  }

  greet3 = function() {
    console.log('greet3', this);
  }
}


let bla = new Greeter();

Edit: javascript output from the compiled typescript:

var Greeter = /** @class */ (function () {
function Greeter() {
    var _this = this;
    this.greet2 = function () {
        console.log('greet2', _this);
    };
    this.greet3 = function () {
        console.log('greet3', this);
    };
    this.greet();
    this.greet2();
    this.greet3();
}
Greeter.prototype.greet = function () {
    console.log('greet1', this);
};
return Greeter;
}());
var bla = new Greeter();

My TypeScript version is 3.4.5

Bergi
  • 513,640
  • 108
  • 821
  • 1,164
Combine
  • 2,868
  • 23
  • 28
  • For a start the second and third ones error out. – Jack Bashford May 09 '19 at 08:54
  • When I compile it on my computer - there are no errors (tsc ./test.ts). I can get the compiled javascript and run it in the browser console with no problems. – Combine May 09 '19 at 08:56
  • @Combine Well if you look at the compilation result, the differences ought to be obvious. – Bergi May 09 '19 at 09:14
  • @Bergi I would not ask this question if the answer was clear to me. – Combine May 09 '19 at 09:17
  • @Combine Did you look at the compiler output? Can you post it? – Bergi May 09 '19 at 09:19
  • I just added it to the question @Bergi – Combine May 09 '19 at 09:20
  • 1
    Great! Now you can see that [`greet` is put on the prototype but `greet3` is put on each instance in the constructor](https://stackoverflow.com/q/310870/1048572), and that both use [dynamic `this`](https://stackoverflow.com/q/3127429/1048572) while [`greet2` is an arrow function with lexical this](https://stackoverflow.com/q/34696686/1048572). – Bergi May 09 '19 at 09:26
  • Related: [How to use arrow functions (public class fields) as class methods?](https://stackoverflow.com/q/31362292/1048572) – Bergi Oct 29 '20 at 20:43

2 Answers2

13

There are differences between all 3 versions. This differences are in 3 areas:

  1. Who is this at runtime
  2. Where the function is assigned
  3. What is the type of this in typescript.

Lets start with where they work just the same. Consider this class, with a class field:

class Greeter {
  constructor(private x: string) {
  }
  greet() {
    console.log('greet1', this.x);
  }

  greet2 = () => {
    console.log('greet2', this.x);
  }

  greet3 = function () {    
    // this is typed as any 
    console.log('greet3', this.x);
  }
}

let bla = new Greeter(" me");

With this class all 3 function calls will print as expected: 'greet* me' when invoked on bla

bla.greet()
bla.greet2()
bla.greet3()

Who is this at runtime

Arrow functions capture this from the declaration context, so this in greet2 is always guaranteed to be the class instance that created this function. The other versions (the method and function) make no such guarantees.

So in this code not all 3 print the same text:

function call(fn: () => void) {
  fn();
}

call(bla.greet) // greet1 undefined 
call(bla.greet2) //greet2 me
call(bla.greet3) // greet3 undefined

This is particularly important when passing the function as an event handler to another component.

Where the function is assigned

Class methods (such as greet) are assigned on the prototype, field initializations (such as greet2 and greet3) are assigned in the constructor. This means that greet2 and greet3 will have a larger memory footprint as they require an allocation of a fresh closure each time Greeter is instantiated.

What is the type of this in typescript.

Typescript will type this as an instance of Greeter in both the method (greet) and the arrow function (greet2) but will type this as any in greet3. This will make it an error if you try to use this in greet3 under noImplictAny

When to use them

  1. Use the method syntax if this function will not be passed as an event handler to another component (unless you use bind or something else to ensure this remains the instance of the class)

  2. Use arrow function syntax when your function will be passed around to other components and you need access to this inside the function.

  3. Can't really think of a good use case for this, generally avoid.

Titian Cernicova-Dragomir
  • 157,784
  • 15
  • 245
  • 242
4

this keyword difference:

In the above all three have same this but you will see the difference when you will pass the method to another functions.

class Greeter {
  constructor() {
  }
  greet() {
    console.log(this);
  }

  greet2 = () => {
    console.log(this);
  }

  greet3 = function() {
    console.log(this);
  }
}


let bla = new Greeter();
function wrapper(f){
  f();
}
wrapper(bla.greet) //undefined
wrapper(bla.greet2) //Greeter 
wrapper(bla.greet3) //undefined

But there is another difference that the first method is on the prototype of class while other two are not. They are the method of instance of object.

class Greeter {
  constructor() {
  }
  greet() {
    console.log('greet1', this);
  }

  greet2 = () => {
    console.log('greet2', this);
  }

  greet3 = function() {
    console.log('greet3', this);
  }
}

let bla = new Greeter();
console.log(Object.getOwnPropertyNames(Greeter.prototype))

If I have in the class -> str = "my string"; and in all the 3 methods I can say console.log(this.str) and it outputs the "my string". But I wonder - is this really actually the same thing

No they are not same things. As I mentioned that greet2 and greet3 will not be on Greeter.prototype instead they will be on the instance itself. It mean that if you create 1000 instances of Greeter their will be 1000 different method(greet2 and greet3) stored in memory for 1000 different instances. But there will a single greet method for all the instances.

See the below snippet with two instances of Greeter()

Community
  • 1
  • 1
Maheer Ali
  • 32,967
  • 5
  • 31
  • 51
  • If I have in the class -> str = "my string"; and in all the 3 methods I can say console.log(this.str) and it outputs the "my string". But I wonder - is 'this' really actually The same thing in all 3 variants of the method ? – Combine May 09 '19 at 09:03
  • 1
    @Combine I have added more explanation in the end of answer. Please check it. – Maheer Ali May 09 '19 at 09:09
  • 1
    What do you mean there is no difference regarding `this`, arrow functions capture `this` regular functions don't, huge implications if you pass the method in a function variable .. – Titian Cernicova-Dragomir May 09 '19 at 09:12
  • It still says "*all three have same this*" which is evidently incorrect? – Bergi May 09 '19 at 09:27
  • @Bergi I mean in the given code when the OP is calling the function from `constructor` all three have same `this` – Maheer Ali May 09 '19 at 09:28