Introduction to TypeScript (Part 3)

Part 3 of a Four-Part Guide for Experienced JavaScript Developers

Ashok Khanna
5 min readMay 29, 2022

In Part 2, we covered parameter deconstructions, type guards and type assertions, which are relatively simple concepts, but helped point us towards a path of thinking in TypeScript. In this Part, we will cover many useful concepts around the typing of functions, objects and classes.

Function Typing

We covered the basics of Function Typing in Part 1, but let us cover some additional topics like optional parameters and function overloading.

Optional Parameters

An interesting point about JavaScript is that functions can take a variable number of arguments. Paraphrasing the ECMA standard

  • If a function or constructor is given fewer arguments than required, the function or constructor shall behave exactly as if it had been given sufficient additional arguments, each such argument being the undefined value
  • If a function or constructor described in this clause is given more arguments than specified, the extra arguments are evaluated by the call and then ignored by the function

We can handle optional parameters in TypeScript in two ways. Like in other languages, we can denote parameters as optionals with the use of the ? operator:

function f(x?: number) { ... }

An alternate way is to simply supply a default value for the parameter (which will be used in the parameter is not passed through in a function call):

function f(x = 5) { ... }

The above concepts of optional types and default values apply to properties in objects and classes as well.

Function Overloading

This leads us to our next concept of function overloading. This is something where TypeScript does differ slightly from JavaScript as the first two items below represent overload signatures and would not be valid JavaScript otherwise (think of them as akin to type declarations, with a different syntax):

// Function Signatures - Deleted from TypeScript during transpilationfunction makeDate(timestamp: number): Date;function makeDate(m: number, d: number, y: number): Date;// Function implementation - what gets used in JavaScriptfunction makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
// implementation
}

The TypeScript Reference Manual notes that we should prefer union types over function overloads where possible.

Function Type Expressions

We round out this section by quickly showcasing how you can create function type expressions (bolded parts below), which are useful when typing functional parameters:

function greeter(fn: (a: string) => void) {
fn("Hello, World!");
}
// or alternativelytype GreeterFunction = (a: string) => void
// Then utilising these:
function greeter(fn: GreetFunction) { ... }

Object Typing

Index Signatures

Sometimes we want to restrict the keys of objects or arrays to either string or numbers (usually they can be both) and/or restrict the type of objects contained within them (we can restrict to any type). We can do that with an index signature as follows:

interface StringArray { [index: number]: string; }// ortype NumberArrayWithStringKeys { [index: string: number ;}// another exampletype OnlyBooksArray { [index: string]: Book; }

Readonly Properties

We mark object properties as readonly with the following:

interface SomeType { readonly prop: string }

Intersection Types

If we want a type to have all the properties of two component types, we can use the & operator:

type ColorfulCircle = Colorful & Circle;

Extending Types

We can extend object types (i.e. create a more specific version that has extra properties) with the extends keyword:

interface AddressWithUnit extends BasicAddress {
unit: string;
}

Note that we use extends with interface and & with type aliases to achieve something similar. There are some subtle differences on how conflicts are handled.

Tuple Types

Below are examples of tuples types in action. Tuple types are useful in heavily convention-based APIs, where each element’s meaning is “obvious”. This gives us flexibility in whatever we want to name our variables when we destructure them.

type StringNumberPair = [string, number]function doSomething(pair: [string, number]){
// Both are equivalent
const [a, b] = pair;
a = pair[0];
b = pair[1];
..do something with a & b
}

Class Typing

Abstract Types

Classes, methods and fields in TypeScript may be abstract, which means they do not have any provided implementation and must exist inside an abstract class which cannot be directly instantiated. The role of abstract classes is to serve as a base class for subclasses which do implement all the
abstract members. When a class doesn’t have any abstract members, it is said to be concrete.

abstract class Base {
abstract getName(): string;

printName() {
console.log("Hello, " + this.getName());
}
}
const b = new Base(); // Error

class Derived extends Base {
getName() {
return "world";
}
}

const d = new Derived(); // Okay

Implementing Interfaces

You can use an implements clause to check that a class satisfies a particular interface. It’s important to understand that an implements clause is only a check that the class can be treated as the interface type. It doesn’t change the type of the class or its methods at all.

interface Pingable {
ping(): void;
}

class Sonar implements Pingable {
ping() {
console.log("ping!");
}

Public, Private and Protected Class Members

public members can be accessed anywhere, private members can only be accessed within the class whilst protected members are only accessible within the class and its subclasses.

class Base {
private x = 0; // <-- Only the Base class can access
public printX() { console.log(x); } <-- Anybody can call this protected greeting() { console.log("Hi") } <-- Subclasses can call
}

The default visibility of class members is public. These visibility modifiers also work on static members (e.g. private static x = 0).

The “this” keyword in TypeScript

The this keyword in JavaScript & TypeScript can be confusing at times, and I won’t try and explain it here (I myself am not very good with it). You can read more about it here.

Constructor Shorthand in TypeScript

One way in which TypeScript adds to JavaScript is providing the below shorthand for class constructors (note that this would be otherwise illegal JavaScript code if we simply stripped out the types):

class Params {
constructor(public readonly x: number, protected y: number,
private z: number)
{
// No body necessary
}
}

Rounding it Out: Structural Comparisons

To add one final comment before we round out Part 3, note that TypeScript compares classes and other types structurally in the sense that the below two classes can be used in place of each other because they are identical structurally:

class Point1 {  x = 0;  y = 0; }

class Point2 { x = 0; y = 0; }

// OK
const p: Point1 = new Point2();

Similarly, TypeScript will implicitly regard a subtype relationship to exist between the following two classes, despite no explicit inheritance:

class Person {
name: string;
age: number;
}

class Employee {
name: string;
age: number;
salary: number;
}

// OK
const p: Person = new Employee();

Next Steps

This part was a very practical look at function, object and class types and we are now relatively well placed in our understanding TypeScript. In the final part 4, we will discuss generic types and how to create and manipulate our own custom types. These tools are helpful in adding more specific typing to our code and also making our types build upon each other so that we can refactor our types with greater ease (useful as our programs get larger). Think of it as a chapter in Type Programming. Take a quick break and lets march on over to the finish line!

--

--

Ashok Khanna

Masters in Quantitative Finance. Writing Computer Science articles and notes on topics that interest me, with a tendency towards writing about Lisp & Swift