Getting Started With TypeScript
Written by Mike James   
Thursday, 12 April 2018
Article Index
Getting Started With TypeScript
The Type System
Interface Inheritance

Class

The TypeScript class construct is much what you would expect. You can define a class:

class MyClass{
}

and the body of the class can contain functions, variables and objects. A constructor of the same name as the class is declared using:

constructor(parameters)

A class can inherit from another class, or as we shall see later from an interface using the extends or  implements keyword. To call the base class constructor you use the super keyword.

Members of the class can be declared as static and these belong to the constructor function and so appear to belong to the class rather than the instance. You have to use the this keyword when working with class or instance members. 

You can override inherited methods and properties by simply redefining them.

Instance members can be declared as public or private with the default being public.

This sounds a wonderful step forward because JavaScript objects can't have private members but as it turns out neither can TypeScript objects. The private declaration is taken notice off only at compile time and if you try to access a private member then you will generate a compile time error. There are ways of dynamically accessing private members at runtime without generating an error.

Classes are implemented in JavaScript by the creation of a standard constructor function of the same name. Inheritance is implemented by chaining prototype objects.

Once again most of this is available in standard JavaScript if you use the appropriate idiom. TypeScript simply provides a perhaps easier and clearer way of writing it.

The Type System

Of course it is the type system that makes TypeScript either worth using or not. By comparison the rest is fairly simple stuff. Imposing a type system on a superset of JavaScript is no simple matter because to contain JavaScript objects can dynamically change their type.

In JavaScript there is no concept of a fixed type - objects simply acquire and lose the methods and properties they need. In addition to this difficulty functions are first class objects, i.e. they are object you happen to be able to call and evaluate, and this makes the concept of type even more sophisticated.

What do JavaScript programmers do to cope with this situation?

They resort to duck typing. Forget the ideas of a type hierarchy based on inheritance. All that matters is that an object has the properties and methods you want to use.

Interestingly this is the approach that TypeScript takes, but it is heavily disguised as static hierarchical typing.

In TypeScript a type is simply a specification for a set of methods and properties an object has. It can be taken as a statement of what it is safe to use and what the object is expected to support.

The type system starts with any, which you can consider to be the top of the type hierarchy, but it is best thought of as a statement that nothing is known about the type. In other words, you can use any method or property in any way that you like and no compiler errors will be generated. Clearly if we are going to rely on type checking we need to use any as little as possible.

The primitive types are number, boolean, string, null and undefined and they directly correspond to the JavaScript primitive types.

To allow functions to act as procedures i.e. to not return a result, we also have the special type void which means the absence of a value.

Type Inference

Whenever possible the system will infer a type for a variable. For example,

var i=1;

allows the system to infer that i is of type Number. After this assigning say a string generates a compile time error.

Type inference is impressive but it can be a problem.
Consider the following:

 

  if (false) {
        var i = 2;
    } else {
        var i = "hello";
    }

 

This is perfectly valid JavaScript and the type of i is clearly String. However in TypeScript i is typed as Number and the else clause generates a runtime error.

Perhaps the message is that you shouldn't write code like this, but you also need to be aware that type inference is lexical and not semantic.

You can explicitly type a variable using a type annotation. For example

var i:number=1;

sets i to be of type number and initializes it to one.

Clearly using explicit type annotation is a better idea than relying on type inference.

You can also form arrays of types using type[ ] - for example number[ ] is a numeric array.

Function Signatures

Functions are just objects you can call but in TypeScript you can specify the functions signature - the types of the parameters and return value.

Notice that the return value is included in the signature - this is unusual.

Functions can also have required and optional parameters, marked by a trailing ? within the signature. You can also include a rest parameter which accepts any additional parameters - it has to be of type any[]

For example:

function myFunction(total:number,
                    name:string):string;

defines a function with a signature number,string return string.  The function

function myFunction() :void;

accepts no parameters and returns no result.

When you call a function the parameter types have to match the signature.

Notice that if you don't specify a signature then one is inferred and type checking is performed using it.

You can also use function overloading i.e. same function with different signatures. In this case you can call the function without error if the call matches any of the signatures you specify.

For example:

function myFunction(x:number) :void;
function myFunction(x:string) :void;

This sounds exciting and useful but you have to implement the overloaded function in the original JavaScript style i.e. you have to work out what the types actually are in the call:

function myFunction(x:number) :void;
function myFunction(x:string) :void;
function myFunction(x: any): void {

    if (typeof x == "string") {
     do something with string
    } else {
    do somethign with number
    }
};

 

The type of the function doesn't include the actual implementation using any.

Arrow Function Expressions

You can use a short cut to defining a function and this is part of ECMA 6, so it is part of modern JavaScript.

For example

(x)=> x+1;

is equivalent to

function(x){return x+1};

There are other variations on this form.

Interfaces

Once we move beyond primitive types we need to look at the interface.

An interface is a set of type definitions e.g. member definitions without implementations.

For example:

interface myInterface {
    i: number;
    myFunction(x:number) :void;
}

defines an interface with a public variable i a number and a method myFunction with the specified signature.

Interfaces can extend existing interfaces by using the extends keyword. As interfaces can extend multiple interfaces, there are some rules about what happens when the something with the same name is inherited.

If you define a class so that it implements an interface, this is a promise that the class supports all the methods and properties of the interface. You can then use the interface type to define variables and function signatures etc and only objects that implement the interface can be used.

For example

class myClass implements myInterface{
 public definitions for i and myfunction
}

you can now write:

var myObj:myInterface=new myClass();

If myClass didn't implement myInterface then there would be a compile time error.

You can think of declaring a variable as an interface type as a promise that it will support all of the method and properties of the interface.

Now we come to some subtle points.

To be of the same type as an interface all that an object has to satisfy is having the required set of members of the correct type and signature. That is even if an object doesn't implement an interface type inference can deduce that it is of that type.  What this means is that types work at compile time much like they do at run-time.

There is even notion of a type hierarchy in TypeScript but it is a very lose and ad-hoc one. A type is a subtype of another type if it contains at least all of the properties and methods of the supertype.

In other words this is a formalization of the JavaScript duck typing. If an object has all of the properties and methods of a type then it can be used in place of that type.

For example if you try:

var test:myInterface = { i: 0};

then the inferred type of the object literal is i:number and you will see an error because myInterface also needs myFunction(x:number) :void;

You can make the assignment work by adding the missing method:

var test: myInterface = {
        i: 0,
        myFunction:
         function (x:number) {
          do something }
    };

 

but you can also add additional methods and properties without triggering a compile time error

var test: myInterface = {
        i: 0,
        myFunction:
          function (x:number) {
            do something },
        j:3
    };

You can see that an interface is a promise to implement at least all of the variables and functions it defines.



Last Updated ( Thursday, 12 April 2018 )