Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
Functions are small blocks of code that takes in some inputs and may return some output or have side effects. A side effect is when a function modifies some variable outside the function. We need functions to organize code into small blocks that are reusable. Without functions, if we want to re-run a piece of code, we have to copy it in different places. Functions are critical to any TypeScript program. In this article, we look at how to define TypeScript functions, how to add types to functions, and passing in arguments in different ways.
Defining Functions
To define a function in TypeScript, we can do the following:
function add(a: number, b: number){
return a + b;
}
The function above adds 2 numbers together and returns the sum. a
and b
are parameters, which lets us pass in arguments into the function to compute and combine them. add
is the name of the function, which is optional in JavaScript. The return
statement lets the result of the function be sent to another variable or as an argument of another function. This is not the only way to define a function. Another way is to write it as an arrow function:
const add = (a: number, b: number) => a + b;
The 2 are equivalent. However, if we have to manipulate this
in the function, then the 2 aren’t the same since the arrow function will not change the value of this
inside the function, like the function defined with the function
keyword does. We can assign the function to a variable since functions are objects in TypeScript. Note that the function above only works if it’s one line since the return is implicitly done if the arrow function is one line. If it’s more than one line, then we write it with brackets like so:
const add = (a: number, b: number) => {
return a + b;
}
This way we have to write the return
statement explicitly. Otherwise, it won’t return a value.
In both examples above, we have the parameter and then the type following each parameter. This is because in TypeScript, if a parameter isn’t followed by a type, then the type will be inferred as having the any
type, which will be rejected if we have the noImplicitAny
flag set when we compile the code.
Function Types
To add more checks for the types of the parameters that are passed in and also the return type of the function, we can add a type designation to the function explicitly. We can do this by writing the signature and then adding the fat right arrow, and then appending the return type of the function after that. For example, we can write the following to designate the type of our add
function:
const add: (a: number, b: number) => number =
(a: number, b: number) => a + b;
The type designation of the add
function is:
(a: number, b: number) => number
in the code above. If we put different types for the parameter than the ones designated by the type we wrote, then the TypeScript compiler will raise an error and refuse to compile the code. For example, if we write:
const add: (a: number, b: number) => number =
(a: number, b: string) => a + b;
Then we get the error:
Type '(a: number, b: string) => string' is not assignable to type '(a: number, b: number) => number'.Types of parameters 'b' and 'b' are incompatible.Type 'number' is not assignable to type 'string'.(2322)
from the TypeScript compiler. If we don’t write out the type explicitly, TypeScript is still smart enough to infer the type from what’s given on the right side of the assignment operator, so we don’t have to write it out explicitly. It saves us effort from having to type everything to use TypeScript.
Calling a Function
If we are referencing and using a function, then we are calling a function.
To call the function, we write:
add(1, 2) // 3
Since our function returns a value, if we console log the return value of the add
function:
console.log(add(1, 2)) // logs 3
We can also assign the return value to a variable:
const sum = add(1, 2);
console.log(sum) // logs 3
Part of a Function
All functions have some or all of the following parts:
-
function
keyword, which is optional - the function name, which is optional
- parentheses — this is required for any function.
- the parameter inside the parentheses, which is optional
- opening and closing curly brackets — required for all functions except for single line arrow functions
-
return
statement, which is optional. If a function doesn’t have areturn
statement, it returnsundefined
. Areturn
statement which has nothing following it will end the executing of the function, so it’s handy for controlling the flow of your function.
Using Arguments
As we can see, many functions have arguments. Arguments are data that is passed into a function for computation, so if we have a function call add(1, 2)
, then 1 and 2 are the arguments.
On the other hand, parameters are what we write in the parentheses when we define a function to clarify what we can pass in as arguments.
We do not have to define a function parameters to pass in arguments since we have the arguments
object in each function. However, it is not recommended since it’s unclear what you want to pass in. We can use the arguments
for optional things that we want to pass in, however.
For example, if we go back to the add
function we defined above:
function add(a: number, b: number){
return a + b;
}
a
and b
are parameters. When we call it by writing add(1, 2)
. 1 and 2 are arguments.
We can specify up to 255 parameters when we define a function. However, we shouldn’t do define more than 5 usually since it becomes hard to read.
Pass in Arguments by Value
There are 2 ways to pass in arguments in TypeScript. One way is to pass in arguments by value. Passing by value means that the arguments passed in are completely separate from the variables you pass in.
The content of the variable is copied into the argument and is completely separate from the original variable if we pass in a variable to a function.
The original variable won’t change even if we change the argument variables that we passed in.
Primitive data types like string, number, boolean, undefined and the null
object is passed in by value in TypeScript.
For instance, if we have the following code:
const a = 1;
const addOne = (num: number) => num + 1
const b = addOne(a);
console.log(b);
a
would still be 1 even after we call addOne
on a
since we make a copy of a
and set it to num
when we are passing in a
as the argument for the call to addOne
.
Pass in Arguments by Reference
Non-primitive are passed into a function by reference, which means that the reference to the object passed in as the argument is passed into the function. The copy of the content is not made for the argument and the passed in object is modified directly.
For example, if we have the following code:
let changeObj = (obj: { foo: string }) => obj.foo = 'bar'
const obj = {
foo: 'baz'
}
changeObj(obj);
console.log(obj); // logs {foo: "bar"}
The original obj
object is defined as { foo: 'baz' }
. However, when we pass obj
into the changeObj
function, where the passed in obj
argument is changed in place. The original obj
object that we passed in is changed. obj.foo
becomes 'bar'
instead of 'baz'
as it’s originally defined.
Missing Arguments
You do not need to pass in all the arguments into a function in TypeScript. Whatever argument that’s not passed in will have undefined
set in its place. So if you don’t pass in all the arguments, then you have to check for undefined
in the arguments so that you don’t get unexpected results. For example, with the add
function that we had:
function add(a, b){
return a + b;
}
If we call add
without the second argument by writing add(1)
, then we get NaN
since 1 + undefined
is not a number.
Default Function Parameter Values
We can set default function parameters for optional parameters to avoid unexpected results. With the add
function, if we want to make b
optional then we can set a default value to b
. The best way to do it is:
function add(a: number, b: number = 1){
return a + b;
}
In the code above, we set b
to 1 so that we if we don’t pass in anything for b
, we automatically get 1 by default for the b
parameter. So if we run add(1)
we get 2 instead of NaN
.
An older, alternative way to do this is to check if the type of the parameter is undefined
like so:
function add(a: number, b: number){
if (typeof b === 'undefined'){
b = 1;
}
return a + b;
}
This achieves the same purpose as the first function, but the syntax is clumsier.
TypeScript functions allow us to organize code into small parts that can be reused. There’re many ways to define a function, but sticking to the commonly recommended ways like using arrow functions and not using arguments
too much is recommended. We will continue to look at TypeScript functions in the next part of this series.