# 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:
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.
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.
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):
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.
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.
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:
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
.
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:
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.