Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Difference between TypeScript Class Function, Class Instance Function and Static Function

Posted on: 2017-05-01

This look like a very basic question to ask. What is the difference between the three different kind of functions you can have in a class? First, some people may be surprise to know that there is more than two. Let's see some code and look at the difference between the three ways to have function.

class User {
  private name: string;
  constructor(initialName: string) {
    this.name = initialName;
  }
  public greet(): string {
    return "Hello, " + this.name;
  }

  public assign = (s: string, x?: string): void => {
    this.name = s;
    if (x === undefined) {
      console.log("Undefined");
    }
  };

  private static count = 0;
  public static assignStatic() {
    User.count = User.count + 1;
  }
}

let greeter = new User("Patrick");

This example has a class function called "greet". This is the most common way to create function in TypeScript. It feels natural from people coming from C# because it's almost the same declaration. You start with the level of encapsulation you want, followed by the name of the function and the parameters and return type.

Under the hood, if you compile with EcmaScript 5 and lower, you will see:

User.prototype.greet = function () {
  return "Hello, " + this.name;
};

The default way to create function create a prototype function. A prototype JavaScript function is shared between all instance of your class, but the this point to the actual class. Hence, the value is not shared. Why TypeScript is using prototype by default is about performance. Prototype functions are faster and use less memory. So, now, you may have figure out that the next way to create function, the instance function will not use the prototype. And, that right. Instance function setup the method inside the instance. This means that if you have 1000 instances of your class that this method will be created 1000 times. Here is the output:

this.assign = function (s, x) {
  _this.name = s;
  if (x === undefined) {
    console.log("Undefined");
  }
};

You can see that this has been changed to use _this which is defined by the closure of the instance of the class.

You can see the whole JavaScript generated by the TypeScript code above under this paragraph.

var User = (function () {
  function User(initialName) {
    var_this = this;
    this.assign = function (s, x) {
      _this.name = s;
      if (x === undefined) {
        console.log("Undefined");
      }
    };
    this.name = initialName;
  }
  User.prototype.greet = function () {
    return "Hello, " + this.name;
  };
  User.assignStatic = function () {
    User.count = User.count + 1;
  };
  return User;
})();
User.count = 0;
var greeter = new User("Patrick");

The third way to create a function is by setting this one as static. A static function share the same information and function between every instance. The way it's translated is that it set the function at the root of the closure of the type. This is true for the function and for static variable.

When to use which one really depend of what you want to do. The easiest one to understand is static. You need static only if you want to share between instance data. While static may be a performance improvement in C# for method, it's still true in TypeScript BUT you already have that gain with the default class function that generate prototype function. So the main question is when to use an instance function. The main scenario is that this method will be used in a callback mechanism where the this pointer may get lost. While there is many pattern around like creating a wrapper function.

class User {
  private name: string;
  constructor(initialName: string) {
    this.name = initialName;
  }
  public greet(): void {
    console.log("Hello, " + this.name);
  }
  public greet2 = (): void => {
    console.log("Hello2, " + this.name);
  };
  public assign = (s: string, x?: string): void => {
    this.name = s;
    if (x === undefined) {
      console.log("Undefined");
    }
  };
  private static count = 0;
  public static assignStatic() {
    User.count = User.count + 1;
  }
}
let greeter = new User("Patrick");
window.setTimeout(greeter.greet, 10); // this won't be the user this
window.setTimeout(() => greeter.greet(), 1000); // this will be user this
window.setTimeout(greeter.greet2, 2000); // this will be user this

Be aware that I changed a little bit the method and added a greet2 that is an instance function. You can see that I use inside these methods to log out the string. The output is as expected that the first one won't be able to output the name. The reason is that this is in the context of the window and not the user. Here is the output:

Hello, Hello, Patrick Hello2, Patrick

A rule of thumb is to use class function as much as possible since they are optimized to not waste too much resource. About callback, using the arrow anonymous function is a great way if you do not need to release the pointer to this event. You may have to release memory manually and the easiest way in would be to use instance function. As you can see, it's not always black or white, but your line of though should be always to think about memory first.