What is exactly the 'static side' and the 'instance side' in typescript?

There is a very similar question, but there is no direct answer to what static side and instance side are referred to in the documentation.

As i vaguely understand, the static side is a constructor and instance side is everything else?

An example from typescript documentation:

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick(): void;
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

What is static side and instance side in this example? Why is a need to use such pattern?

1 answer

  • answered 2019-10-15 18:45 jcalz

    Naming in computer science is notoriously difficult.

    In TypeScript, we generally use the same name to refer to the constructor of a class (which is a value that exists at runtime), and as the type of the instances of the class (which only exists at design time and is not emitted to JavaScript).

    So in

    class Foo { } 
    const foo: Foo = new Foo();
    

    there is a class constructor named Foo, which can be used to construct class instances whose type we call Foo.

    This is a bit like using the term "Lego" to refer both to the company that manufactures plastic construction toys, and to the construction toys themselves. Or like "Toyota" as the name of a company and the name of the vehicles that they make. We say "Toyota makes and sells Toyota vehicles" or "Lego makes and sells Lego construction toys" without misunderstanding, but the analogous "Foo constructs Foo instances" might be confusing. So be it, I guess.


    When we talk about a class, we might be talking about its static aspects, which pertain to the constructor (generally singular, "the constructor", because there's just one of them, which is the same for all instances), or we might be talking be talking about its instance aspects, which pertain to the instances of the class (generally plural, "instances", because the single constructor can make many instances, each of which has possibly different state or properties from others).

    From the type system's standpoint, the type Foo corresponds to the shape of the instance side. To get the type of the static side, we can use the typeof type query operator operating on the constructor value named Foo: typeof Foo. So, Foo is the instance side type and typeof Foo is the static side type.

    Let's augment Foo to show how this works:

    class Foo {
    
      instanceProp: string;
    
      constructor(constructorArgument: string) {
        this.instanceProp = constructorArgument;
      }
    
      instanceMethod(): void {
        console.log("Instance method called on " + this.instanceProp)
      }
    
      static staticProp: string = "Static Thing";
    
      static staticMethod(): void {
        console.log("Static method called on " + this.staticProp)
      }
    
    }
    

    That's a single class definition, but it has a static side and an instance side. The static side of Foo, that is, things pertaining to its constructor, includes:

    • the constructor itself, whose signature is {new(constructorArgument: string): Foo}
    • the staticProp property, whose type is string, and
    • the staticMethod method, whose signature is {(): void}.

    This type is called typeof foo, but you can actually define your own interface for it if you want, like so:

    interface StaticSideOfFoo {
      new(constructorArgument: string): Foo;
      staticProp: string;
      staticMethod(): void;
    }
    

    The instance side of Foo, that is, things pertaining to its instances, includes:

    • the instanceProp property, whose type is string, and
    • the instanceMethod method, whose signature is {(): void}.

    This type is called Foo, and you can define your own interface for this also:

    interface InstanceSideOfFoo {
      instanceProp: string;
      instanceMethod(): void;
    }
    

    Let's make sure we understand the difference:

    const foo1 = new Foo("Number 1");
    console.log(foo1.instanceProp); // Number 1
    foo1.instanceMethod(); // Instance method called on Number 1
    
    const foo2 = new Foo("Number 2");
    console.log(foo2.instanceProp); // Number 2
    foo2.instanceMethod(); // Instance method called on Number 2
    
    console.log(Foo.staticProp); // Static Thing
    Foo.staticMethod(); // Static method called on Static Thing
    

    Note that foo1 and foo2 have no direct access to the constructor signature or to staticProp or staticMethod:

    new foo1("oops"); // error!
    foo1.staticProp; // error!
    foo1.staticMethod(); // error!
    

    and that Foo has no direct access to instanceProp or instanceMethod:

    Foo.instanceProp; // error!
    Foo.instanceMethod(); // error!
    

    And the values foo1 and foo2 have type Foo, which is similar to InstanceSideOfFoo, whereas the value Foo has type typeof Foo, which is similar to StaticSideOfFoo. We can verify that here:

    const instanceSideOfFoo: InstanceSideOfFoo = foo1; // okay
    const instanceSideOfFooOops: InstanceSideOfFoo = Foo; // error!
    
    const staticSideOfFoo: StaticSideOfFoo = Foo; // okay
    const staticSideOfFooOops: StaticSideOfFoo = foo1; // error!
    

    It helps to be able to talk about these different sides because they have different shapes and different uses. If you want to make a function that accepts the Foo constructor, you want to talk about typeof Foo or StaticSideOfFoo or new (x: string)=>Foo, whereas if you want to make a function that accepts an instance of Foo, you want to talk about Foo or InstanceSideOfFoo or {instanceProp: string}, etc.

    Without such a distinction you might end up trying to buy the Lego corporation for your children or becoming a shareholder in a 1997 Toyota Camry. Or maybe not, but the analogous mistakes in TypeScript would be too easy to make. What can I say, naming things is tough.

    Okay, hope that helps; good luck!

    Link to code