TypeScript function


May 07, 2021 21:00 TypeScript


Table of contents


The TypeScript function describes

Functions are the foundation of JavaScript applications. I t helps you implement abstraction layers, simulation classes, information hiding, and modules. I n TypeScript, functions are still the primary place to define behavior, although classes, namespaces, and modules are already supported. TypeScript adds additional functionality to the JavaScript function, making it easier for us to use.

Function

Like JavaScript, TypeScript functions can create functions with names and anonymous functions. You're free to choose the right approach for your application, whether it's defining a series of API functions or using them only once.

You can quickly recall the functions in these two JavaScripts with the following example:

// Named function
function add(x, y) {
    return x + y;
}

// Anonymous function
let myAdd = function(x, y) { return x + y; };

In JavaScript, functions can use variables outside the function body. W hen the function does this, we say it 'captures' these variables. As to why this can be done and the pros and cons beyond the scope of this article, a deep understanding of this mechanism can be helpful for learning JavaScript and TypeScript.

let z = 100;

function addToZ(x, y) {
    return x + y + z;
}

The type of function

Define a type for a function

Let's add a type to the function above:

function add(x: number, y: number): number {
    return x + y;
}

let myAdd = function(x: number, y: number): number { return x+y; };

We can add a type to each argument before adding a return value type to the function itself. TypeScript automatically infers the return value type from the return statement, so we usually omit it.

Write the full function type

Now that we've specified the type for the function, let's write out the full type of the function.

let myAdd: (x:number, y:number)=>number =
    function(x: number, y: number): number { return x+y; };

The function type consists of two parts: the argument type and the return value type. B oth parts are required when writing out the full function type. W e write out the parameter types as a list of parameters, specifying a name and type for each parameter. T he name is just for added readability. We can also write this:

let myAdd: (baseValue:number, increment:number) => number =
    function(x: number, y: number): number { return x + y; };

As long as the argument type is matched, it is considered a valid function type, not whether the argument name is correct or not.

The second part is the return value type. F or return values, we use the symbol before the function and the return => to make it clear. As mentioned earlier, the return value type is a necessary part of the function type, and if the function does not return any values, you must also specify void type is void and cannot be left blank.

The type of function consists only of the argument type and the return value. T he capture variables used in the function are not reflected in the type. In fact, these variables are hidden states of functions that are not part of the API.

Infer the type

When you try this example, you'll find that if you specify a type on one side of the assignment statement but there is no type on the other, the TypeScript compiler automatically recognizes the type:

// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return x + y; };

// The parameters `x` and `y` have the type number
let myAdd: (baseValue:number, increment:number) => number =
    function(x, y) { return x + y; };

This is called "categorization by context" and is a type inference. It helps us better specify types for programs.

Optional and default parameters

Every function argument in TypeScript is required. T his does not mean null undefined but rather that the compiler checks whether the user has passed in values for each parameter. T he compiler also assumes that only these parameters are passed into the function. In short, the number of arguments passed to a function must match the number of arguments expected by the function.

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result3 = buildName("Bob", "Adams");         // ah, just right

In JavaScript, each parameter is optional and can be passed on. W hen there is no ginseng, its value is undefined. I n TypeScript we can use ? next to the parameter name T he ability to implement optional parameters. For example, we want last name to be optional:

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

let result1 = buildName("Bob");  // works correctly now
let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result3 = buildName("Bob", "Adams");  // ah, just right

Optional parameters must be followed by required parameters. If we want first name to be optional in the example above, we have to adjust their position and put first name behind us.

In TypeScript, we can also provide a default value for a parameter when the user does not pass the parameter or undefined T hey are called parameters with default initialization values. Let's modify the example above to set the default value of last "Smith"

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined);       // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result4 = buildName("Bob", "Adams");         // ah, just right

All parameters with default initialization after the arguments are optional and, like the optional parameters, can be omitted when the function is called. This means that the optional argument shares the parameter type with the default argument at the end.

function buildName(firstName: string, lastName?: string) {
    // ...
}

And

function buildName(firstName: string, lastName = "Smith") {
    // ...
}

Share the (firstName: string, lastName?: string) => string The default value of the default parameter disappears, retaining only the information that it is an optional parameter.

Unlike normal optional parameters, parameters with default values do not need to be placed after the required parameters. I f an argument with a default value appears in front of the required parameter, the user must undefined value to get the default value. For example, let's rewrite the last example firstName is an argument with a default value:

function buildName(firstName = "Will", lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result3 = buildName("Bob", "Adams");         // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams");     // okay and returns "Will Adams"

The remaining parameters

The necessary parameters, the default parameters, and the optional parameters have one thing in common: they represent a particular parameter. S ometimes you want to manipulate multiple parameters at the same time, or you don't know how many parameters will pass in. In JavaScript, you can use arguments to access all incoming parameters.

In TypeScript, you can collect all the parameters into one variable:

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

The remaining parameters are treated as an unlimited number of optional parameters. Y ou can have none of them, and you can have any of them. T he compiler creates an array of parameters with the name you are omitting ... . Given a name later, you can use this array inside the function body.

This oddity is also used on the function type definition with the remaining parameters:

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

this

Learning to use this in this is like an adult ritual. S ince TypeScript is a superset of JavaScript, TypeScript programmers also need to figure this and be able to find out what's wrong when there's a bug. F ortunately, TypeScript can notify you that this is used this I f you want to understand how this works in this first read Yehuda Katz's Understanding JavaScript Function Invocation and "this". Yehuda's article this works internally, so let's do a brief introduction here.

this arrow functions

In JavaScript, this is specified only when the function is called. T his is a powerful and flexible feature, but you need to take a moment to figure out what the context of a function call is. But as we all know, this is not an easy task, especially when returning a function or passing it as an argument.

Here's an example:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

You createCardPicker is a function, and it returns another function. I f we try to run the program, we will find that it does not pop up the dialog box but is wrong. B ecause this in the function returned by createCardPicker this set to window of deck object. B ecause we just called cardPicker() T he top-level non-method call this window (Note: In strict mode, this undefined window

To solve this problem, we can tie the correct this when the function is this I n this case, no matter how you use it later, the bound 'deck' object is referenced. W e need to change the function expression to use the ECMAScript 6 arrow syntax. The arrow function can save the this value when the this not the value at the time of the call:

Let's change the function expression to using the lambda expression ( ( ) . This specifies the 'this' value when the function is created, not when the function is called.

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

Better still, TypeScript will warn you of a mistake if you set the --noImplicitThis It will this.suits[pickedSuit] is any this

this parameter

Unfortunately, this.suits[pickedSuit] is still any T his this comes from a function expression in the object literal quantity. T he method of modification is to provide an explicit this parameter. this is a false argument that appears at the top of the argument list:

function f(this: void) {
    // make sure `this` is unusable in this standalone function
}

Let's add some interfaces, Card Deck to the example to make type reuse clearer and simpler:

interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

Now TypeScript knows that createCardPicker to be Deck a Deck object. That this Deck type, any --noImplicitThis not mislisted.

this argument is in the callback function

You can also see this error in this functions when you pass a function to a library function and it is called later. B ecause when callbacks are called, they are called as a normal function, this be undefined W ith a few changes, you can this with this parameter. First, the author of the library function this type of this:

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

this: void means that addClickListener expects onclick to be a function that does not require a this type. Second, annotate your calling code with this :

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // oops, used this here. using this callback would crash at runtime
        this.info = e.message;
    };
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!

After you this type, you explicitly declare that onClickBad Handler instance. T ypeScript then detects addClickListener the function to have this: void Change this type to fix this error:

class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // can't use this here because it's of type void!
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);

Because onClickGood this type as void it addClickListener O f course, this also means that you this.info . If you want both, you have to use the arrow function:

class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}

This is possible because the arrow function does this so you can always pass them on to this: void void. T he disadvantage is Handler object creates an arrow function. M ethods, on the other hand, are created only once and Handler prototype chain. They are Handler different Handler objects.

Overload

JavaScript itself is a dynamic language. It is common for functions in JavaScript to return different types of data based on different parameters passed in.

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

pickCard method returns two different types depending on the incoming parameters. I f an object is passed in to represent a card, the function is to grab a card from it. I f the user wants to catch a card, we tell him what card he caught. But how does this mean in a type system?

The method is to provide multiple function type definitions for the same function to overload the function. T he compiler handles the call to the function based on this list. Let's overload the pickCard function.

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

When this is changed, the overloaded pickCard performs the correct type check at the time of the call.

To enable the compiler to select the correct type of check, it is similar to the processing process in JavaScript. I t looks for the overload list and tries to use the first overload definition. U se this if it matches. Therefore, when defining overload, be sure to put the most precise definition first.

Note that function pickCard(x): any part of the overload list, so there are only two overloads here: one is the receiving object and the other receives the number. Calling pickCard with other pickCard an error.