Daniel Earwicker Chief Software Architect
FISCAL Technologies Ltd

TypeScript - What is a class?


TYPESCRIPT 2016-09-11

In TypeScript, a class is really two types and a function. One type is the type of the function itself, and the other type is the type of the object returned when you call the function. Try this:

class C {
    foo() {}
}

const c1: typeof C = C;
const c2: C = new C();
const c3: C = C;

Note that c3 is a type error. C is not of type C. C is a function, a named object that exists at runtime, and like any identifier its type can be written as typeof C, so c1 has to work. But at compile time, C is an interface, conformed to by the object we get back from new C(), so that's why c2 works.

Interfaces are used in TypeScript to describe anything that has properties. In JavaScript a function can have properties of its own, so the definitive way to describe a function is with an interface. This lambda-like style:

type F = (arg1: number, arg2: string) => string

is an abbreviation for:

interface F {
    (arg1: number, arg2: string): string
}

It looks just like a method, except without a name. So if we have f that implements F, we can directly call it:

f(5, "hi");

But it may be that we can also say f.blah(), because F also has a blah property that happens to be another function:

interface F {
    (num: number, str: string): { num: number, str: string };
    blah(): void;
}

Another thing we can only do with the interface style of function-type is to define multiple ways to call the function:

interface F {
    (num: number, str: string): { num: number, str: string };
    (num: number): boolean;
}

And finally (especially relevant here), we can say that the function must be called with the new prefix:

interface F {
    new (num: number, str: string): { num: number, str: string };
}

Though we can also do that with the lambda-like version:

type F = new (num: number, str: string) => { num: number, str: string };

Putting these pieces together, suppose we have class

class C {
    i = 5;
    static s = "hi";
}

// Usage:
console.log(C.s);
console.log(new C().i);

We can completely describe the type of C "by hand" like this:

interface TypeOfC {

    new (): {
        i: number;
    }

    s: string;
}

So TypeOfC describes a new-able function, because we declare that it has new-able function with no name. s is a property of the function, whereas i is a property of the objects created when you call the function.

We can declare a const of type TypeOfC and assign C to it:

// Compatible:
const c2: TypeOfC = C;

// Usage:
console.log(c2.s);
console.log(new c2().i);

Now, we can abbreviate our TypeOfC re-declaration because TypeScript already gives a name to the type returned from the function: it's called C:

interface TypeOfC {

    new (): C
    s: string;
}

Recall what we established right at the start: when you write a class C, you give two meanings to that name. In the namespace of types, you create a type C that describes the objects returned by new C(). In the namespace of runtime objects, you create a function C. TypeScript never confuses the two meanings because every spot in your code is unambiguously referring to either a compile-time type or a runtime object.

And we don't need to declare TypeOfC at all, because we can just use TypeScript's built-in feature for getting the type of a named object: typeof C. In JavaScript, typeof takes an expression and returns a string describing its type (to an extent…) In TypeScript that is still exactly the same, of course, but also typeof can be used in type declarations to get the type of an expression.

Hence:

// Compatible:
const c2: typeof C = C;

// Usage:
console.log(c2.s);
console.log(new c2().i);
Time reversible events 2023-04-07
Language Smackdown: Java vs. C# 2023-03-07
Domesday '86 Reloaded (Reloaded) 2021-02-07
The Blob Lottery 2020-09-27
Abstraction is a Thing 2020-03-07
Unfortunate Bifurcations 2019-11-24
Two Cheers for SQL 2019-08-26
Factory Injection in C# 2019-07-02
Hangfire - A Tale of Several Queues 2019-05-24
How Does Auth work? 2018-11-24
From Ember to React, Part 2: Baby, Bathwater, Routing, etc. 2018-03-18
From Ember to React, Part 1: Why Not Ember? 2017-11-07
json-mobx - Like React, but for Data (Part 2) 2017-02-15
Redux in Pieces 2017-01-28
Box 'em! - Property references for TypeScript 2017-01-11
TypeScript - What's up with this? 2017-01-01
MobX - Like React, but for Data 2016-12-28
Eventless - XAML Flavoured 2016-12-24
Immuto - Epilogue 2016-12-20
Immuto - Radical Unification 2016-09-22
Immuto - Working with React (An Example) 2016-09-16
Immuto - Strongly Typed Redux Composition 2016-09-11
TypeScript - What is a class? 2016-09-11
TypeScript and runtime typing - EPISODE II 2016-09-10
TypeScript and runtime typing 2016-09-04
What's good about Redux 2016-07-24
TypeScript multicast functions 2016-03-13
Introducing doop 2016-03-08
TypeScript is not really a superset of JavaScript and that is a Good Thing 2015-07-11
A new kind of managed lvalue pointer 2014-04-27
Using pointer syntax as a shorthand for IEnumerable 2014-04-26
Adding crazily powerful operator overloading to C# 6 2014-04-23
Introducing Carota 2013-11-04
Want to comment on anything? Create an issue!