Functions are small blocks of code that take in some inputs and may return some output or have side effects. A side effect means that it modifies some variables 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 continue to look at different parts of TypeScript functions, including passing in a variable amount of arguments, recursion, function nesting, and defining functions in objects.
Calling Functions with More Arguments that Parameters
In TypeScript, we can call a function with more arguments than parameters. If we just pass them in without accessing them from the argument
object, they’ll be ignored. You can get the extra arguments that aren’t in the parameters with the argument
object and use them. The argument object has the parameters with numerical keys just like the indexes of an array. Another way to access extra arguments is through the rest parameter.
For example, if we call the add
function with extra parameters:
function add(a: number, b: number, ...rest: any){
console.log(arguments);
return a + b;
}
add(1, 2, 3);
The ...rest
part of the signature captures the parameters that we don’t expect to be passed in. We used the rest operator, which is indicated by the 3 periods before the word rest
to indicate that there might be more parameters at the end after b
. We need this in TypeScript so that we don’t get the mismatch between the number of parameters and the number of arguments passed in. In vanilla JavaScript, ...rest
is optional.
In the console.log
call, we should get:
0: 1
1: 2
2: 3
Variable Scope in Functions
Functions inside shouldn’t be accessible outside of functions unless they are global variables. We should avoid defining global variables as much as we can to prevent bugs and hard to trace errors since they can be accessed anywhere in the program. To prevent defining global variables, we should use let
to define variables and const
to define constants. For example, we should define functions like so:
function add(a: number, b: number){
let sum = a + b;
return sum;
}
In this case, we have sum
which is only accessible within the function since it’s defined with the let
keyword.
Anonymous Functions
Anonymous are functions with no names. Since they have no name, they cannot be referenced anywhere. They are often passed into other functions as callback functions, which is called when the function is passed into an argument. However, you can assign anonymous functions into a variable so it becomes a named function.
They can also be self executing. This is means that you can define the function and make it run immediately. For example, if we write:
const sum = (function(a: number, b: number){
return a + b;
})(1, 2);
console.log(sum) // log 3
We log 3 because we defined a function to add 2 numbers, and then passed in 1 and 2 as the arguments immediately after by wrapping the function in parenthesis and then passed the arguments to it.
Recursion
You can call the same function from within itself in TypeScript. This is called recursion. All recursive functions must have an end condition, which is called the base case, so that it knows when it stops executing. Otherwise, you can get a function that’s called an infinite number of times, which will crash the browser.
To write a recursive function, we can write:
function sumOfSquares(num: number): number {
let sum: number = Math.pow(num, 2);
if (num == 1) {
return 1
} else {
return sum + sumOfSquares(num - 1)
}
}
In this example, we wrote a function to compute the sum of squares for a given number. We compute the square of num
and then if we have num
equal to 1 then we return 1. Otherwise, we return the sum of sum
plus the result of call sumOfSquares
on num — 1
. We keep reducing num
so that we can reach our base case of 1, adding up the results while doing so.
Nesting Functions
Functions can be nested within each other. This means that we can define a function inside another function. For example, we can write:
function convertToChicken(name: string){
function getChickenName(name: string){
return `Chicken ${name}`;
}
return getChickenName(name)
}
In this case, we called getChickeName
inside the convertToChicken
call. So if we write convertToChicken('chicken')
, then we get 'Chicken chicken'
since we called get getChickeName
and returned the result. The scope of variables is the name. let
and const
are block-scoped so they cannot be accessed outside of the original function that’s defined, but they are available in the nested function, so if we have:
function convertToChicken(name: string) {
let originalName = name; function getChickenName(newName: string) {
console.log(originalName)
return `Chicken ${newName}`;
}
return getChickenName(name)
}
Then originalName
will still be defined in the console.log
.
Defining Function in an Object
We can define a function in an object in a few ways. We can use the function
keyword or arrow function as usual, but we can also write it with a shorthand for the function
keyword. For example, if we have a bird
object and we want to define the chirp
function, we can write:
const bird = {
chirp: function(){
console.log('chirp', this)
}
}
or use the following shorthand:
const bird = {
chirp(){
console.log('chirp', this)
}
}
The 2 are the same since the chirp
function will have the bird
object as the value of this
.
On the other hand, if you use an arrow function:
const bird = {
chirp: () => {
console.log('chirp', this)
}
}
We’ll get an error from the Typescript compiler because the value of this
is the globalThis
value, which the TypeScript compiler doesn’t allow. We get the error “The containing arrow function captures the global value of ‘this’.(7041)” when we try to compile the code above.
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.