TypeScript Functions

Welcome to this tutorial on TypeScript Functions. It will show you how to get started with the following concepts:

  • Function Basics
  • Functions & TypeScript’s Type System
  • Anonymous Functions
  • Named Functions
  • Optional Parameters
  • Rest Parameters
  • Function Overloads
  • Arrow Functions

The example code was written using VS Code and the TypeScript 1.5 compiler, for a tutorial on how to setup a VS Code – TypeScript workflow, se the end of the post. The example code is available on my GitHub repository, link available at the end of the post.

Function Basics

As TypeScript is just a super-set of JavaScript, the most basic function in TypeScript is just a JavaScript function.
Create a TypeScript file and enter the following code, an anonymous function that returns a string. It will compile without errors.

var anonFunc = function(howFar) {
    return "A long time ago in a Galaxy" + howFar + " away...";
};

The compiled JavaScript will look just the same.

var anonFunc = function (howFar) {
    return "A long time ago in a Galaxy" + howFar + " away...";
};

Functions & TypeScript’s Type System

Taking advantage of TypeScript’s real strength, the type system, we are able to specify the types of incomming parameters as well as any return type of a function.

Anonymous Funtions

We used one in the first example, but anonymous functions can also be typed, both parameters as well as return value.
Using TypeScript’s type annotations would translate our previous example to:

var anonFunc = function(howFar: string): string {
    return "A long time ago in a Galaxy" + howFar + " away...";
};

console.log(anonFunc("far, far"));

Now this is where TypeScript starts to shine! Being able to type all your JavaScript is what enables compile time checks, tooling help, refactoring possibilities etc. So how exactly how much are we paying for all these development time goodies, cause surely we are paying for this with more verbose JavaScript code and a runtime overhead? Let’s have a look at the generated JavaScript code and see.

var anonFunc = function (howFar) {
    return "A long time ago in a Galaxy" + howFar + " away...";
};
console.log(anonFunc("far, far"));

Voila, no difference.

The compiler works the typing magic mostly at runtime, hence no difference in the code here.

But try calling the code with for ex a number instead of a string

typedAnonFunc(5);

If you’re using an IDE supporting TypeScript, like for ex Visual Studio or VS Code, there will be a squiggly under the parameter. Running the TypeScript compiler will give a response saying it’s called with the wrong type
error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

 

Named Functions

A named function is defined in TypeScript just like it is in JavaScript, below a function taking an array of strings, having no output and thus being marked with return type void.

var droidNames: Array<string> = ["C3-PO", "R2-D2"];

function vadersOrders(droids: Array<string>): void {
	console.log("Find the following droids:");
	droids.forEach(element => {
		console.log(element);
	});
}

vadersOrders(droidNames);

The emitted JavaScript code looks about the same but with no types.

var droidNames = ["C3-PO", "R2-D2"];
function vadersOrders(droids) {
    console.log("Find the following droids:");
    droids.forEach(function (element) {
        console.log(element);
    });
}
vadersOrders(droidNames);

However, if it’s compiled with ES6 as target, we get the the same code as the TypeScript code, but without typing:

var droidNames = ["C3-PO", "R2-D2"];
function vadersOrders(droids) {
    console.log("Find the following droids:");
    droids.forEach(element => {
        console.log(element);
    });
}
vadersOrders(droidNames);

 

Optional Parameters

TypeScript supports optional parameters in function signatures, something familiar for those coming from a C# background. Optional Parameters are of use when comming from a JavaScript background where it’s sometimes hard to get used to TypeScripts more rigid ways of declaring and calling functions.
Optional Parameters are denoted with a question mark, let’s see an example.

function mostAnnoyingGungan(topDog: string, goodSecond?: string){
	console.log("Really annoying Gungan: " + topDog );
	if (goodSecond !== undefined) {
		console.log("And don't forget " + goodSecond);
	}
}

mostAnnoyingGungan("Jar Jar Binks");
mostAnnoyingGungan("Jar Jar Binks", "Rugor Nass");

The JavaScript here is almost identical, types removed as usual and the Optional Parameter there as a regular named parameter.

function mostAnnoyingGungan(topDog, goodSecond) {

Rest Parameters

Since JavaScript functions can always be called with an optional number of arguments, no matter how the function signature look, there is a way of iterating over parameters. This is done with the arguments object, an Array like object (not a real Array, mind you) containing all given parameters.
In TypeScript we instead use Rest Parameters. Rest Parameters enables us to give a function an optional number of parameters, from 0 to n. They are denoted in code as prefixed with three ellipsis before the parameter name, and they have to be given as the very last entry in the parameter list.

function jediFreeForAll(firstCombatant: string, secondCombatant: string, ...additionalBrawlers: Array<string>) {
	var numberOfCombatants = (additionalBrawlers.length + 2);
	var winner = Math.floor((Math.random() * numberOfCombatants) + 1);
	console.log(numberOfCombatants + " Jedi's enter the ring, and the winner is:");
	if (winner === 1) {
		console.log(firstCombatant);
	} else if (winner === 2) {
		console.log(secondCombatant);
	} else {
		console.log(additionalBrawlers[winner - 3]);
	}
}

jediFreeForAll("Yoda", "Darth Maul");
jediFreeForAll("Yoda", "Darth Maul", "Ben Kenobi", "Luke Skywalker");

Here we let the Jedi’s battle it out and TypeScript Handily collect all Rest Parameters in an Array for us to access from within our function.

Now if we compile this with ES5 as target, the JavaScript is getting interesting. To handle Rest Parameters, TypeScript’s compiler creates an array of the same name as our given Rest Parameter.

function jediFreeForAll(firstCombatant, secondCombatant) {
    var additionalBrawlers = [];
    for (var _i = 2; _i < arguments.length; _i++) {
        additionalBrawlers[_i - 2] = arguments[_i];
    }
    var numberOfCombatants = (additionalBrawlers.length + 2);
    var winner = Math.floor((Math.random() * numberOfCombatants) + 1);
    console.log(numberOfCombatants + " Jedi's enter the ring, and the winner is:");
    if (winner === 1) {
        console.log(firstCombatant);
    }
    else if (winner === 2) {
        console.log(secondCombatant);
    }
    else {
        console.log(additionalBrawlers[winner - 3]);
    }
}
jediFreeForAll("Yoda", "Darth Maul");
jediFreeForAll("Yoda", "Darth Maul", "Ben Kenobi", "Luke Skywalker");

However when compiling the code for ES6, which supports Rest Parameters, we get:

function jediFreeForAll(firstCombatant, secondCombatant, ...additionalBrawlers) {
    var numberOfCombatants = (additionalBrawlers.length + 2);
    var winner = Math.floor((Math.random() * numberOfCombatants) + 1);
    console.log(numberOfCombatants + " Jedi's enter the ring, and the winner is:");
    if (winner === 1) {
        console.log(firstCombatant);
    }
    else if (winner === 2) {
        console.log(secondCombatant);
    }
    else {
        console.log(additionalBrawlers[winner - 3]);
    }
}
jediFreeForAll("Yoda", "Darth Maul");
jediFreeForAll("Yoda", "Darth Maul", "Ben Kenobi", "Luke Skywalker");

Function Overloads

TypeScript also supports Function Overloads like found in many object oriented languages.
To define an overloaded function we need first to specify the function overload signatures, then define the function but that signature needs to be generic enough to cover the defined overloads. This means resorting to the TypeScript any type. Let’s look at an example where we’re unsure of if we’re getting handed a name or a serial number:

function bestowRoyalAward(droidId: string): string;
function bestowRoyalAward(droidId: number): string;
function bestowRoyalAward(droidId: any): string {
	if (typeof droidId === "string") {
		return droidId + " says Thanks!";
	} else if (typeof droidId === "number") {
		var droidName = getDroidName(droidId);
		if (droidName !== "") {
			return droidName + " says Thanks!";
		} else {
			return "No such droid!";
		}
	}
}

function getDroidName(droidSerial: number): string {
	switch (droidSerial) {
		case 12345:
			return "MSE-6";
		default:
			return "";
	}
}

console.log(bestowRoyalAward(12345));
console.log(bestowRoyalAward("R2-D2"));
console.log(bestowRoyalAward(555));

Nothing special is going to be added in the JavaScript code, the parameter is already dynamic there so all good.

function bestowRoyalAward(droidId) {
    if (typeof droidId === "string") {
        return droidId + " says Thanks!";
    }
    else if (typeof droidId === "number") {
        var droidName = getDroidName(droidId);
        if (droidName !== "") {
            return droidName + " says Thanks!";
        }
        else {
            return "No such droid!";
        }
    }
}
function getDroidName(droidSerial) {
    switch (droidSerial) {
        case 12345:
            return "MSE-6";
        default:
            return "";
    }
}
console.log(bestowRoyalAward(12345));
console.log(bestowRoyalAward("R2-D2"));
console.log(bestowRoyalAward(555));

Arrow Functions

The Arrow Function is a shorthand way of writing a function expression where it’s not necessary to use the function keyword. Arrow Functions are also referred to as Fat Arrow Functions (-> = skinny arrow, => = fat arrow) and Lambda Functions, because of other languages, like c#.

A few examples to show the syntax:

( ... ) => expression

Is equal to

( ... ) => { return expression; }

And

(x) => expression

Is equal to

x => expression

Let’s tweak and reuse a function from the example on Function Overloads:

function droidName(droidSerial: number): string {
    switch (droidSerial) {
        case 12345:
            return "MSE-6";
        default:
            return "Error!";
    }
}

Now we’ll use this function to show all allowed permutations of using an Arrow Function calling it.
All the following are correct expressions and equal:

( droidId ) => droidName(droidId)
( droidId ) => { return droidName(droidId); }
droidId => droidName(droidId)
droidId => { return droidName(droidId); }

Capturing the enclosing context

Now there’s one very special thing about Arrow Functions, they lexically capture the value of this.
Since all functions defined their own this and with the advent of OO practices in JavaScript it became quite frustrating to not be able to reach properties defined in the parent scope in an easy way. A common pattern to solve this was to define a new variable named self to keep track of this.
But with the introduction of the Arrow Function that pattern is no longer necessary, let’s look at an example.

function wompRatBlaster() {
    this.hit = 0;
    this.miss = 0;

    setInterval(function startGame() {
    // setInterval(() => {
        var hit = Math.random() > 0.5;
        if (hit) {
            console.log("hits: " + ++this.hit);
        } else {
            console.log("misses: " + ++this.miss);
        }
    }, 500);
}

var game = new wompRatBlaster();

After starting the WompRatBlaster™ game it will print out hits or misses to the console, but since the this.hit and this.miss is out of scope and not reachable from the startGame function (it has it’s own this scope and hence this.hit and this.miss will be undefined when the function is first called), all the hits and misses will be NaN.

This is the compiled JavaScript for the example above:

function wompRatBlaster() {
    this.hit = 0;
    this.miss = 0;
    setInterval(function startGame() {
        var hit = Math.random() > 0.5;
        if (hit) {
            console.log("hits: " + ++this.hit);
        }
        else {
            console.log("misses: " + ++this.miss);
        }
    }, 500);
}
var game = new wompRatBlaster();

Changing the call to an arrow function in arrowFunction.ts, like this:
'setInterval(function startGame() {'
replaced with
'setInterval(() => {'

would instead bring the hit and miss properties into scope and the method would start counting the score as planned.

The compiled JavaScript code for that would be the following, where we can see the compiler saving the this scope in the _this variable.

function wompRatBlaster() {
    var _this = this;
    this.hit = 0;
    this.miss = 0;
    setInterval(function () {
        var hit = Math.random() > 0.5;
        if (hit) {
            console.log("hits: " + ++_this.hit);
        }
        else {
            console.log("misses: " + ++_this.miss);
        }
    }, 500);
}
var game = new wompRatBlaster();

The code is compiled to ES5 JavaScript, for ES6 the code would look just like the TypeScript code since Arrow Functions are supported (yay!).

In closing

I hope you liked this brief tutorial of Functions in TypeScript and that it will be of help when learning TypeScript basics. There are more exiting stuff coming up for Functions in TypeScript 1.6, namely generators. But that is subject for another article in the future.

Get The Code!

All code available under my GitHub repository in the repository called: TypeScript_Functions. When running the examples, be aware that not all browsers have implemented support for some of the ES6 (or EcmaScript2015) features demoed. Microsoft Edge currently supports all of the code in these examples however.

More TypeScript projects

Tutorial for setting up a TypeScript – MVC 6 app in VS2015: Getting started: TypeScript 1.5 in Visual Studio 2015

Tutorial for setting up a TypeScript – MVC 6 app in VS Code: Getting started: TypeScript 1.5 in Visual Studio Code

If you are interested in a more advanced setup of a TypeScript – MVC 6 app that uses RequireJS in VS2015, check out this post: Setting up a TypeScript – RequireJS – Gulp – MVC 6 Web Project in Visual Studio 2015 RC

Happy coding! 🙂

TypeScript Functions
Tagged on:             

4 thoughts on “TypeScript Functions

Leave a Reply

Your email address will not be published. Required fields are marked *