Skip to main content

Functions in TypeScript

3 min read

Older Article

This article was published 9 years ago. Some information may be outdated or no longer applicable.

Functions in TypeScript work exactly like functions in ES2015. Fat arrow syntax, default parameters, the spread operator. All there.

But TypeScript is a superset of JavaScript. It extends ES2015 with data types and interfaces.

Combine those features and you get something genuinely useful: code that tells your IDE exactly what’s going on. You can define argument types for your function and specify what datatype it returns. If it returns nothing, slap on the void keyword:

function add(n: number, m: number): number {
  return n + m;
}

function fight(weapon: string): void {
  // no return
  console.log(`Warrior fights with ${weapon}`);
}

You can also type rest parameters. This is ideal for numerical operations where you want to guarantee every argument is a number:

function add(...numbers: number[]): number {
  return numbers.reduce((acc, number) => acc + number);
}

add(1, 2);
add('1', 2); // Argument of type '"1"' is not assignable to parameter of type 'number'

Interfaces and arrow functions

Now let’s throw interfaces and arrow functions into the mix:

interface IWarrior {
  name: string;
  weapon: string[];
  health: number;
  pickWeapon(this: IWarrior): () => Weapon;
}

interface Weapon {
  name: string;
  hitPower: number;
}

const warrior: IWarrior = {
  name: 'Dave the Nomad',
  weapon: ['sword', 'knife', 'epic axe', 'spear'],
  health: 100,
  // NOTE: The function now explicitly specifies that its callee must be of type Deck
  pickWeapon: function (this: IWarrior) {
    return () => {
      const warriorWeapon = Math.floor(Math.random() * this.weapon.length);
      const weaponHitPower = Math.floor(Math.random() * 100);
      return { name: this.weapon[warriorWeapon], hitPower: weaponHitPower };
    };
  },
};

const myWarrior = warrior;
const weaponChoice = myWarrior.pickWeapon();
const weapon = weaponChoice();
console.log(`My warrior is ${myWarrior.name}. (Health: ${myWarrior.health})`);
console.log(
  `Randomly selected weapon: ${weapon.name} with hit power ${weapon.hitPower}.`
);

Picture a game where you create warriors and randomly assign each one a weapon with a hit power.

We set up two interfaces: one for the warrior, one for the weapon. Notice how pickWeapon() takes this as a parameter typed to IWarrior. That forces this to be IWarrior instead of any, which means the compiler catches type mismatches.

The pickWeapon() method defined in IWarrior also requires us to return something matching our Weapon interface.

We then build our warrior object: a name, a health value, and an array of weapons. To fulfil the IWarrior interface contract, we add a pickWeapon() method that randomly selects an index from the weapon array, generates a random hit power value, and returns an object with the weapon’s name and hitPower (satisfying the Weapon interface).

A few things to notice here. We’re using interfaces and datatypes throughout. And by using the fat arrow syntax (available in ES2015 too), we can safely use this inside another function. The this keyword (excuse the pun) gets fetched lexically. No need for .bind(), .apply(), or that old trick of assigning this to another variable.