Key Concepts of TS - FUNCTIONS

2023-05-17 19:40:08 浏览数 (2)

# Description

TS has classes, namespaces, and modules, but functions play a key role. The TS language slightly expands the capabilities of functions compared to JavaScript, making working with them even more convenient.

代码语言:javascript复制
// Name function
function add(x: number, y: number): number {
  return x   y;
}

// Anonymous function
const subtract = function(x: number, y: number): number {
  return x - y;
}

// Arrow function
const multiply = (x: number, y: number): number => x * y;

You can add types to each parameter, as well as to the function itself, to specify the type of the return value. TS can infer the type of the return value itself by analyzing the return statements, so it is often possible not to specify it explicitly.

# Types of Functions

# Adding Types to Function

You can add types to each parameter, as well as to the function itself, to specify the type of the return value.

代码语言:javascript复制
function add (x: number, y: number): number {
  return x   y;
}

// full type of function
const divide:(x: number, y: number) => number = function (x: number, y: number): number {
  if (y === 0) {
    throw new Error('Cannot divide by zero');
  }
  return x / y;
}

A functional type consists of two parts: the argument types and the return type. The type of the return value is determined after =>. In the event that the function does not return any value, void must be specified.

# Inferring Types

代码语言:javascript复制
// myAdd has the full function type
const myAdd = function(x: number, y: number): number {
  return x   y;
}
// Parameters 'x' and 'y' - has 'number' type
const myAdd2: (baseValue: number, increment: number) => number =
  function(x, y) {
    return x   y;
  }

This is called contextual typing – a type inference. This feature allows you to spend less effort on adding types to the program.

There are several places in TS where type inference is used to get information about types without explicitly specifying it. For example, the following code:

代码语言:javascript复制
let x = 3;

The type of the variable x is output in number. This kindof inference occurs when initializing variables and members, assigning default values to parameters, and defining the type of the function’s return value.

# Best General Type

代码语言:javascript复制
let x = [0, 1, null];

To output the type x in this case, you need to check the type of each element in the array. In this case, there are two options for the array type: number and null. The algorithm for finding the best common type checks each candidate type, and selects the one that is compatible with all the others.

Since the best common type must be chosen from the types provided, there are cases where the types have a common structure for all, but none of them is the base for all the others.

代码语言:javascript复制
let zoo = [new Rhino(), new Elephant(), new Snake()];

Ideally, we would like the zoo type to be output as Animal[] (that is, an array of objects of the Animal – animal class). But, since there is not a single object in the array that has the Animal class, the compiler is not able to get such a result. To fix this, you will have to explicitly specify the type if no object has the base type for all the others:

代码语言:javascript复制
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

If the compiler cannot find the best common type, the output will be the type of an empty object, that is, {}. Since this type has no members, attempting to use any of its properties will result in an error. As a result of this inference, you can still use the object as if its type is unknown, and guarantee type safety in cases where the object type cannot be found implicitly.

# Context Type

Contextual typing occurs when you can make a guess about the type of an expression based on its position.

代码语言:javascript复制
window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button); // Error
};

If this function expression was located where its type could not be inferred from the context, the type of the MouseEvent parameter would be any, and the compiler would not throw an error.

If an expression whose type was inferred from the context contains an explicit type indication, the inferred context type is ignored.

代码语言:javascript复制
window.onmousedown = function(mouseEvent: any) {
  console.log(mouseEvent.button); // Works
};

Contextual typing is used in many cases. Typically, these are arguments when calling functions, the right-hand side of an assignment, type checks, object members and array literals, and return statements.

Also, the context type isused as a candidate for the best general type.

代码语言:javascript复制
// There are four candidates for the best overall type:Animal, Rhino, Elephant, and Snake.
function createZoo(): Animal[] {
  return [new Rhino(), new Elephant(), new Snake()];
}

# Anonymous Functions

代码语言:javascript复制
let addFunc = function(a: number, b: number) : number {
  return a   b;
}
let addFuncResult = addFunc(1, 2);

# Type Compatibility

Type compatibility in TS is based on structural typing.Structural typing is a way to identify type relationships based solely on the composition of their members. This approach differs from nominative typing.

代码语言:javascript复制
interface Named {
  name: string;
}
class Person {
  name: string;
}
let p: Named;
// Everything fits, since the structural type system p = new Person() is used ()

The TS structural type system was designed with the way JavaScript code is usually written in mind. Since JavaScript makes extensive use of anonymous objects, such as function expressions and object literals, it is much more natural to describe their relationships using a structural system rather than a nominative one.

The basic rule of the TS type system is that x is compatible with y if y has at least the same members as x.

代码语言:javascript复制
interface with the name {
  name: string;
}

let x: Named;
// the output type for y is { name: string; location: string }
let y = { name: 'Alice', location: 'Seattle' };
x = y; // OK

To understand whether y can be assigned to x, the compiler searches for the corresponding compatible property in y for each of the properties of x.

The same rule is used in the case of checking arguments when calling a function:

代码语言:javascript复制
function greet(n: By name) {
  alert('Hello, '   n.name);
}
greet(y); // OK

Note that y has an additional location property, but this does not result in an error. When checking for compatibility, only members of the target type (in this case, Named) are considered.

# Optional Parameters and Default Parameters

代码语言:javascript复制
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

Make lastName optional from the previous example:

代码语言:javascript复制
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

All optional parameters must come after the require dones.

TS also allows you to specify a value for a parameter that it will accept if the user skips it or passes undefined. These parameters are called default parameters, or simply default parameters.

代码语言:javascript复制
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

The default parameters that follow all the required parameters are considered optional. Just like the optional ones, you can skip them when calling the function.

代码语言:javascript复制
// they will have the same type
function buildName (firstName: string, lastName?: string) {}

function buildName (firstName: string, lastName = 'Smith') {}

# Rest Parameters

Mandatory, optional, and default parameters have one thing in common – they describe one parameter at a time. In some cases, you need to work with several parameters, treating them as a group; and sometimes it is not known in advance how many parameters the function will take.

In TS, you can assemble arguments into a single variable:

代码语言:javascript复制
function buildName (firstName: string, ...restOfName: string[]) {
  return firstName   ' '   restOfName.join(' ');
}
let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie');

The ellipsis is also used when describing the type of function with residual parameters:

代码语言:javascript复制
function buildName (firstName: string, ...restOfName: string[]) {
  return firstName   ' '   restOfName.join(' ');
}

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

# this Keyword

As a rule, an object method needs access to the information stored in the object in order to perform any actions with it (in accordance with the purpose of the method).

To access information inside an object, a method can use the this keyword.

代码语言:javascript复制
let user = {
  name: 'John',
  age: 30,
  sayHi: function() {
   console.log(this.name);
  }
};
user.sayHi(); // John

# Keyword “this” and Arrow Functions

代码语言:javascript复制
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);

The this is used in the function created by createCardPicker, points to the window and not to the deck object. All this is due to the fact that cardPicker() is called by itself.

You can fix this by making sure that the function is bound to the correct value of this before returning it. To do this, you need to change the function, and use the syntax of the arrow function from the ECMAScript 6 standard.

Arrow functions capture the value of this as it was at the time of its creation (and not at the time of the call):

代码语言:javascript复制
let deck = {
  suits: ['hearts', 'spades', 'clubs', 'diamonds'],
  cards: Array(52),
  createCardPicker: function() {
    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);

Even better, if you pass the – noImplicitThis flag to the compiler, TS will issue a warning if you make a similar error. It will indicate that this in the expression this.suits[pickedSuit] is of type any.

# this Parameters

Unfortunately, the type of the expression this.suits[pickedSuit] is still any, since this is taken from a function expression inside an object literal. To fix this, you can explicitly specify this as a parameter.

代码语言:javascript复制
function f(this: void) {
  // Ensure 'this' cannot be used in this separate function
}

We will add several interfaces to the previous example: Card and Deck, to make the types more understandable and easy to reuse:

代码语言:javascript复制
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);

The compiler now knows that the createCardPicker function expects to be called on an object with the Deck type. This means that the type of this value is now Deck,not any, and the – noImplicitThis flag will not throw errors.

# this Parameters for Callback Functions

You may also encounter this-related errors in callback functions when the functions are passed to a library that will later call them. Since the passed function will be called by the library as a normal function, this will have the value undefined.

With some effort, you can use the this parameter to prevent such errors.

代码语言:javascript复制
interface UIElement {
  addClickListener(onclick: (this: void, e: Event) => void): void;
}

this: void means that addClickListener assumes that the onclick function does not require this. Secondly, the code that is called must also be accompanied by the this parameter:

代码语言:javascript复制
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!

When this is specified, it explicitly reflects the fact that onClickBad must be called on an instance of the Handler class. Now TS will detect that addClickListener requires afunction with this: void.

代码语言:javascript复制
class Handler {
  info: string;
  onClickGood(this: void, e: Event) {
    // can't use this here
    console.log('clicked!');
  };
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood); // won't work!

Since the onClickGood function specifies that the type of this is void, it can be passed to addClickListener. Of course, this also means that it can no longer be used in it this.info. But if you need both, you will have to use the arrow function:

代码语言:javascript复制
class Handler {
  info: string;
  onClickGood = (e: Event) => {this.info = e.message;}
}

# Overloads

TS supports function overloading, meaning we can define multiple versions of a function that will have the same name, but different parameter types or different number of parameters.

For overloading, we first define all versions of the function that will not have any logic. And then we define a version of the function with a common signature that fits all the previously defined options. And in this general version, we already define the specific logic of the function.

代码语言:javascript复制
function add(x: string, y: string): string;
function add(x: number, y: number): number;
function add(x: any, y: any): any {
  return x   y;
}
let result1 = add(5, 7);
console.log(result1); // 12
let result2 = add('5', '7');
console.log(result2); // 57

// let result3 = add(true, false); // error!

Two versions of the function allow you to take either two strings or two numbers as parameters.

JavaScript is by its very nature a very dynamic language.It is not uncommon to find functions that return objects of different types depending on the arguments passed.

In order to select the correct type check, the compiler performs actions similar to those in JavaScript. It looks through the list of overloads, starting with the first element, and matches the function parameters. If the parameters are appropriate, the compiler selects this overload as the correct one. Therefore, as a rule, function overloads are ordered from the most specific to the least specific.

0 人点赞