Previously in "Learn with Me - TypeScript"
Introduction
In the previous article, we have had an introduction to TypeScript and a discussion around its type system. In this article, we will take a look at the functions. We will discuss how to define and work with functions and what TypeScript allows us to do with them.
How to Use the Code
It is expected that an editor and TypeScript compiler is installed in your PC and you know how to compile a typescript file. If you do not have those in place, do not panic. Go to the first part of the series, A Study in Types.
TypeScript Function Basics
Like in JavaScript, TypeScript also allows you to create functions by declaration or by expression.
Defining a Function
Function declaration means defining a function with function
keyword and a valid function name. The function will be accessible by that name. On the other hand, expressed function, not necessarily have a name, is stored in a variable, and the function can be accessed by the variable name.
function declaredFunction () {
}
var expressedFunction = function () {
}
Functions not having a name are called anonymous functions.
Apart from these, TypeScript gives us another way to define a function even without the function
keyword. We can use the arrow operator (=>
) instead.
var arrowOperatorExp = () => {
}
Type Notation
Just because its name is TypeScript, just like for the variables, it allows us to have type notation for the functions as well. We can define static type notation for a function by its return type.
To start with, let's create a TypeScript file in our working folder, named functions_type-notation.ts and put the following code snippet in.
function myFuncDecl1 (): void {
}
var myFuncExp1 = function (): void {
}
var arrowOperatorExp = (): void => {
}
Compile it and have a look into the .js file. Now, add the following lines and try to compile it again.
function myFuncDecl2 (): void {
return true;
}
The compilation error indicates that the compiler does a validation on the return type of the function and the static notation for the function's return type. Let's delete that function and go forward.
TypeScript also allow us to define static type notations for the parameters.
function sayHello(name: string): void{
}
function saySomething(name: string, num: number): void{
}
In TypeScript, when a function is called, it looks for an exact signature with parameter types exactly in the same order as the argument types. Being governed by the rule, unlike JavaScript, once you define static types for the parameters, you cannot call the function with arguments that do not match with the types of the parameters.
sayHello(2);
saySomething(2, "The World");
Both of these lines will raise compilation errors as none of them match with any of the function signatures.
Optional Parameters
In JavaScript, each and every parameter in a function is implicitly an optional parameter. But TypeScript does not provide us with that luxury, which is good. But even if it is good, sometimes the situation demands for parameters to be optional in a function. So, TypeScript allows us to have explicit optional parameters. Wow, how cool is that!!! Developers now have more control not only over the functions but also, on the invocations of the functions.
To continue with this discussion, we will need another TypeScript file named "functions_optional-parameters.ts". Type in the following snippet, and try to compile.
function addNumbersWithOptionalParams(a: number, b: number, c?:number): void {
var res = a + b;
if(c)
{
res += c;
alert('a + b + c = ' + res);
return;
}
alert('a + b = ' + res);
}
addNumbersWithOptionalParams(1);
addNumbersWithOptionalParams(1, 2);
addNumbersWithOptionalParams(1, 2, 3);
The first attempt to invoke the method addNumbersWithOptionalParams()
will cause a compilation error, but the other two calls to the function will not. If we comment out the problematic line, and compile, it will be compiled nicely.
One thing to remember though. While developing a function with optional parameters, they are required to be included after the mandatory parameters in the function signature. In case there are multiple optional parameters, we can use pass arguments for selective parameters. We can exclude passing arguments for the parameters at the end of the function signature. But, if we need to skip some from the middle, we will need to pass either null
or undefined
for those. For example, If we have a function signature as:
function addAndOptionalMultiply (a: number, b: number, c?:number, m?:number): void {};
To use a
, b
and m
, we need to call it as below:
addAndOptionalMultiply (1, 2, null, 3);
Default Parameters
If we define a function with some functional parameters, we need to check if the parameters contain any value or not before using those parameters. Wouldn't it be nice to have some mechanism where we can set a default value to the optional parameters if the caller does not pass any value for them? Yes, besides having optional parameters, TypeScript also allows us to have default parameters. Let's create a new file named "functions_default-parameters.ts". Type in the following snippet and compile:
function addNumbersWithDefaultParameters(a: number, b: number, c:number = 0, d: number = 0): void {
var res = a + b + c + d;
alert('result = ' + res);
}
addNumbersWithDefaultParameters(1, 2);
addNumbersWithDefaultParameters(1, 2, 3)
Because we have used the default parameters in our function, we don't have to check if the optional variable are undefined or not. It comes in handy when we need to change the signature of an existing function with more parameters. Let's have a look at the output JavaScript file as well to have a better understanding about the default parameters.
function addNumbersWithDefaultParameters(a, b, c, d) {
if (c === void 0) { c = 0; }
if (d === void 0) { d = 0; }
var res = a + b + c + d;
alert('result = ' + res);
}
addNumbersWithDefaultParameters(1, 2);
addNumbersWithDefaultParameters(1, 2, 3);
Rest Parameters
We have learned about the optional parameters and we have learned about the default parameters. Both of these have one problem in common - the limitation for passing the arguments. We cannot pass six arguments for a function which takes only four parameters. But not to worry, we have rest parameters to support that. An elipsis before the parameter name and using an array as the type will do the work.
Create a new file named "functions_rest-parameters.ts". Put the following code snippet in and compile.
function addNumbersWithRestParameters(... n: number[]): void {
var result = 0;
var i;
for(i=0; i < n.length; i++) {
result += n[i];
}
alert("Result = " + result);
}
addNumbersWithRestParameters(2);
addNumbersWithRestParameters(2, 2, 2, 2, 2);
Have a look at the JavaScript file:
function addNumbersWithRestParameters() {
var n = [];
for (var _i = 0; _i < arguments.length; _i++) {
n[_i - 0] = arguments[_i];
}
var result = 0;
var i;
for (i = 0; i < n.length; i++) {
result += n[i];
}
alert("Result = " + result);
}
addNumbersWithRestParameters(2);
addNumbersWithRestParameters(2, 2, 2, 2, 2);
Interestingly, there is no parameter in the function. So, how is it receiving the arguments? If you are not familiar with the arguments object in JavaScript, it is a built in object, local to the function which behaves mostly like an array. When a function is called, the arguments
object holds all the arguments and can be accessed from the function.
As you see, though it looks slick in the TypeScript, it introduces a redundant loop for assigning the items from the arguments object to a local variable, hence has a performance issue.
Function Overloading
Function overloading is possible in TypeScript. If you don't know what I am talking about, function overloading is the capability of having more than one functions with the same name but different signature (with different types or different number of parameters).
Just like always, we will write some code. Let's take a file named "functions_overloading.ts" in our working folder. Type the following code in and compile.
function print (n: number);
function print (str: string);
function print (b: boolean);
function print (v: number | string | boolean) {
if(typeof(v) === "number")
{
alert(v + " is a number")
}
else if(typeof(v) === "string")
{
alert("'" + v + "' is a string")
}
else if(typeof(v) === "boolean")
{
alert("'" + v + "' is a boolean")
}
else {
alert("error");
}
}
print(1);
print("Hello World!");
print(true);
Have a look at the output JavaScript file. Doesn't it seem that we overworked to write the TypeScript, it would be lot easier to write a JavaScript function instead? Well, it yes but here is the benefit - the print()
function in the JavaScript is allowed to accept any argument and possibly will often end up with the error. On the other hand, because, TypeScript is typed, you cannot pass any argument with types other than number
, string
and bool
. You can give it a try by adding the following line:
print({id: 1, name: "test"});
Callbacks
Like JavaScript, callbacks are allowed to be passed as arguments in TypeScript. And as everything else, they can be typed as well. Let's see how. Create a new file named "functions_callbacks.ts" and fill it up with the following lines.
var mainFunction = function(callback: () => void){
alert("Main Function");
callback();
}
var callbackFunction = function(): void {
alert("Callback");
}
mainFunction(callbackFunction);
When done, save and compile. Easy, we have a main
function that accepts any void
callback functions and during its execution, it executes the callback. The "Arrow
" function definition comes into good use while working with callbacks.
mainFunction((): void => {
alert("Arrow Function Callback");
})
Immediately Invoked Function
As we learned from the previous episode, A Study in Types, that var
variables are scoped to be local to the function. Immediately Invoked Function is a design pattern that uses this principle to create smaller scopes for variables with limited usage. Create a new file named "functions_iif.ts" and type in the following lines:
function iif_main(): void {
var gVar: number = 1;
(function(v: number){
gVar = v;
var lVar: number = v;
alert("gVar inside IIF=" + gVar);
alert("lVar inside IIF=" + lVar);
})(2);
alert("gVar in mainFunction = " + gVar);
alert("lVar in mainFunction = " + lVar);
}
iif_main();
Compile. lVar
, declared in the immediately invoked function is not available in the outer function's scope.
Conclusion
Now that we know about the basics of the functions, we can enter the object oriented world of TypeScript. But that will be another story.