DEV Community

John Au-Yeung
John Au-Yeung

Posted on • Originally published at thewebdev.info

Introduction to TypeScript Functions — This Object and Overloads

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 take an input and either return an output or have side effects, which means that if modifies 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 to different places. Functions are critical to any TypeScript program. In this part of the series, we continue to look at different parts of TypeScript functions, including how to deal with the this object with TypeScript, and overloading functions.


This Object

If this is referenced in the regular function declared with the function keyword, the this object isn’t set inside an arrow function to the function that has the this inside. If an arrow function is inside a constructor, then this is set to the new object. If it’s not inside an object, then this inside the arrow function is undefined in strict mode. this will be set to the base object if the arrow function is inside an object. However, we always get the window object if we reference this in an arrow function. For example, the following code wouldn’t compile and run since TypeScript doesn’t allow our code to have the global variable as the value for this:

const fn = () => this  
console.log(fn());
Enter fullscreen mode Exit fullscreen mode

We get the window object logged when console.log is run. Likewise, if we ran this:

let obj = {}  
obj.f = () => {  
  return this;  
};  
console.log(obj.f());
Enter fullscreen mode Exit fullscreen mode

We get the same thing as we got before. This is in contrast to the traditional functions declared with the function keyword. Let’s see what happens if we replace the functions above with traditional functions in the code above, as in the following code:

const fn = function() {  
  return this  
};  
console.log(fn);
Enter fullscreen mode Exit fullscreen mode

Now, if we have noImplicitThis flag on in our tsconfig.json, we get the following error from the TypeScript compiler: 'this' implicitly has type ‘any’ because it does not have a type annotation.(2683)

We’re closer to getting our code working, but it’s still not going to compile. To fix this error, we need to put a fake parameter, this: void, in the function signature, as in the code below:

const fn = function(this: void) {  
    return this;  
};  
console.log(fn);
Enter fullscreen mode Exit fullscreen mode

With the code above, we make the this variable unusable. If we want to use it for something, we can add an explicit type for this instead of void, to make it do something. For example, if we want to make a constructor object, we can write something like the following code:

In the code above, we created an interface called Person to add a data type to the this object in the greet function in the person object. When we call the greet function, the this parameter is ignored since we placed it as the first parameter. TypeScript only looks at the type of this in the parameter and will not expect us to call the greet function by passing in an argument for this. After defining the person object, we can then assign values to the name and age properties outside it. We already met the requirements listed in the interface when we’re defining the person object, but we should also assign some meaning value to it so we can use the greet function.

Then, when we run we run the console.log in the last line of the example above, we get Hi Jane. You’re 20 years old. We’ve successfully created a data type for this, so there won’t be any ambiguity as to what the value of this is. This helps developers understand what this has in the code since this is one of the more confusing aspects of JavaScript. In plain JavaScript, this can take on different values depending on where the this keyword is located in the code. In traditional functions, this’s value would be the function itself. Arrow functions do not change the value of this, so whatever it was outside is the same as whatever it is inside the arrow function.

With TypeScript, we can’t use the this and traditional functions to create classes. For example, if we write:

Then we would get the error Cannot find name ‘Person’. Did you mean ‘person’?(2552) and the code won’t compile. TypeScript doesn’t let us use traditional functions as classes. To use make classes, we must use the class syntax.


‘this’ Parameters in Callbacks

For callback functions used for event listeners, the callback functions that we pass in should be typed in an interface. For example, for setting the type of custom input control components, we can write something like Tthis:

interface InputElement {  
  addKeyUpListener(onclick: (this: void, e: Event) => void): void;  
}
Enter fullscreen mode Exit fullscreen mode

Then, when people that use the control, then they can write something like this for it to run:

What if developers that use the library try to reference this, as in the following code?:

In this case, the TypeScript compiler will throw an error since this is marked with the void type in the InputElement interface.


Function Overloads

Overloading a function is creating functions with the same name, but with different signatures. This isn’t allowed in JavaScript since functions are objects and we can’t re-declare the same object multiple times. However, since TypeScript is strongly typed, which is the opposite of the dynamic nature of JavaScript, it has to find a way to accommodate the dynamic aspects of JavaScript. To do this, TypeScript provides us a way to overload functions with different signatures. To overload functions with TypeScript, we just have to write multiple function signatures with the same name before defining the actual function with that name. For example, we can write something like this:

function getPerson(person: { name: string, age: number }): { name: string, age: number };  
function getPerson(person: { name: string }): { name: string };  
function getPerson(person: any): any {  
  return person;  
}
Enter fullscreen mode Exit fullscreen mode

With the code above, we have a getPerson function that can either accept an object with the properties name and age , or just the property name. The return type, which is after the colon, can either be an object with both the name and age properties, or an object with just the name property. This is what we have in the first three lines of the code example above, where we just define the signatures we want for our getPerson function.

Then in the actual getPerson function definition, we have the actual function definition. We annotate the type of the parameter and return as any , which is OK because we already defined the parameters and return types that the getPerson accepts and returns respectively. We can call the getPerson, as in the code below:

console.log(getPerson({ name: 'Jane', age: 20 }));  
console.log(getPerson({ name: 'Jane' }));
Enter fullscreen mode Exit fullscreen mode

From the console.log statements above, we get:

{name: "Jane", age: 20}  
{name: "Jane"}
Enter fullscreen mode Exit fullscreen mode

If we try to call it with an argument that we didn’t define in the signature like: console.log(getPerson({})) we get a No overload matches this call error.

The main way to deal with the this object in traditional functions is to pass it in as the first parameter of a function and then set a type to it by defining an interface for the type. TypeScript will ignore the this parameter and treat it as if it doesn’t exist. It’s only used for setting the data type of this. We can overload functions in TypeScript, which isn’t allowed in JavaScript. To do this, we just define different signatures for the function and give them the same name, then define the function with the same name after we defined the signatures that our function will accept.

Top comments (0)