“This is the 13th day of my participation in the August Text Challenge.
In JavaScript, functions are a cornerstone of building applications, and we can use them to extract reusable logic, abstract models, and encapsulation processes. Functions are still one of the most basic and important concepts in TypeScript. Here’s a look at how function types are defined and used in TypeScript.
1. Function type definition
(1) Directly define the function type
The definition of a function type includes the type definition for parameters and return values:
function add(arg1: number, arg2: number) :number {
return x + y;
}
const add = (arg1: number.arg2: number) :number= > {
return x + y;
};
Copy the code
Here we define the Add function as both a function literal and an arrow function. The function arguments arg1 and arg2 are numeric types, and the resulting sum is also numeric.
If you omit the parameter type, TypeScript defaults the parameter type to any; If you omit the return value type, TypeScript defaults to void if the function has no return value. If the function has a return value, TypeScript deduces the return type based on the defined logic.
Note that in TypeScript, if a function returns no value and we explicitly define the return type as undefined, an error will be reported: A function whose declared type is neither ‘void’ nor ‘any’ must return A value. The correct way to do this is to declare the return type of a function as void:
function fn(x: number) :void {
console.log(x)
}
Copy the code
The definition of a function includes the function name, parameters, logic, and return value. When defining a type for a function, the complete definition should include parameter types and return value types. All of the above are defining the specified parameter types and return value types of the function. Let’s define a complete function type and use this function type to specify the types that the parameters and return values of a function definition must conform to.
let add: (x: number, y: number) = > number;
add = (arg1: number.arg2: number) :number= > arg1 + arg2;
add = (arg1: string.arg2: string) :string= > arg1 + arg2; // error
Copy the code
Here we define a variable add and give it the function type (x: number, y: number) => number. This function type contains the types of parameters and return values. Add is then assigned an actual function whose argument type and return type are the same as those defined in the function type, so it can be assigned. If the function returns a string value, it will return a string value.
Can't put a type"(arg1: string, arg2: string) => string"Assign to type"(x: number, y: number) => number". parameter"arg1"and"x"Type incompatible. Can't put a type"number"Assign to type"string".Copy the code
Note: If a function uses a variable defined outside the function body, the type of the variable is not reflected in the function type definition.
(2) The interface defines function types
Function types can be clearly defined using interfaces. Use the interface to define the type of the add function:
interface Add {
(x: number.y: number) :number;
}
let add: Add = (arg1: string.arg2: string) :string= > arg1 + arg2;
// Error cannot assign type "(arg1: string, arg2: string) => string" to type "Add"
Copy the code
The function type is defined through the interface form, and the interface Add defines that this structure is a function, that both parameters are of type number, and that the return value is of type number. When the variable add is of type add, it must be a function, and the parameter type and return value type must meet the requirements of interface Add. Obviously, this function does not meet the requirements, so an error is reported.
(3) Type aliases define function types
Function types can be defined using type aliases in a more intuitive and readable form:
type Add = (x: number, y: number) = > number;
let add: Add = (arg1: string.arg2: string) :string= > arg1 + arg2;
// Error cannot assign type "(arg1: string, arg2: string) => string" to type "Add"
Copy the code
Use the type keyword to give any defined type an alias. After the Add alias is defined above, Add becomes a type definition consistent with (x: number, y: number) => number. The Add type is specified as Add, but the value assigned to Add does not meet the requirements of the Add type, so an error is reported.
Note that the => here is different from the => of the arrow function in ES6. The => in TypeScript function types is used to represent the definition of a function, with the parameter type on the left and the return value type on the right. In ES6, => is the implementation of the function.
2. Function parameter definition
(1) Optional parameters
TypeScript checks for errors in function arguments when writing code:
type Add = (x: number, y: number) = > number;
let add: Add = (arg1, arg2) = > arg1 + arg2;
add(1.2); // success
add(1.2.3); // Error should have two arguments, but gets three
add(1); // Error should have two arguments, but gets one
Copy the code
In JavaScript, neither of the last two function calls in the above code gives an error, except that add(1, 2, 3) returns the correct result, 3, and Add (1) returns NaN. In TypeScript, we specify parameters, so when we use the type, we must pass in the same type and number of parameters as defined.
Sometimes, however, some parameters of a function are not required, so we can make the parameters of the function optional. Optional arguments need only be followed by a parameter name. You can:
type Add = (x: number, y: number, z? :number) = > number;
let add: Add = (arg1, arg2, arg3) = > arg1 + arg2 + arg3;
add(1.2); // success 3
add(1.2.3); // success 6
Copy the code
The code above, z is an optional parameter, the type which is number | undefined, said parameter z is the default, does that mean to the default and type is undefined equivalent? Consider the following example:
function log(x? :number) {
console.log(x);
}
function log1(x: number | undefined) {
console.log(x);
}
log();
log(undefined);
log1(); // Expected 1 arguments, but got 0
log1(undefined);
Copy the code
As you can see, the third function call failed. : indicates that a function can be called without explicitly passing in arguments. However, if the statement of type number | undefined, it means the function is not the default parameters and type must be a number or undfined.
Note that optional arguments must be placed after required arguments, which is consistent with defining functions in JS. Here’s an example:
type Add = (x? :number, y: number) = > number; // error Mandatory parameters cannot be located after optional parameters.
Copy the code
In TypeScript, optional arguments must come last, and the optional argument x precedes the required argument y. In JavaScript, there is no such thing as an optional parameter, but when writing logic, you may determine whether a parameter is undefined. If so, it means that the function was not passed this parameter, so you need to make compatibility processing. If the first parameter is not passable and the second parameter is passable, you need to pass a undefined placeholder in the position of the parameter that is not passable.
(3) Default parameters
Before the ES6 standard, the default parameters were cumbersome to implement:
var count = 0;
function counter(step) {
step = step || 1;
count += step;
}
Copy the code
Above defines a counter value function, this function has a parameter step, namely each time step, if not the incoming parameters, then step received is undefined, undefined converted to Boolean value is false, so step | | 1 took 1 here, Thus, the default step === 1 is not passed.
In ES6, when you define a function, set the default value of the argument directly after the argument and use the equal sign to connect the default value:
const count = 0;
const counter = (step = 1) = > {
count += step;
};
Copy the code
When a default parameter is specified, TypeScript recognizes the default parameter type. When calling a function, an error is reported if the parameter with the default value is passed another type of parameter:
const add = (x: number, y = 2) = > {
return x + y;
};
add(1."ts"); // Parameters of type "string" cannot be assigned to parameters of type "number"
Copy the code
It is also possible to type the default parameter y explicitly:
const add = (x: number, y: number = 2) = > {
return x + y;
};
Copy the code
Note: The default argument type of a function must be a subtype of the argument type, as follows:
const add = (x: number, y: number | string = 2) = > {
return x + y;
};
Copy the code
Here the add function parameters of y type joint types as optional number | string, but because the default parameters of the digital type is joint type number | string subtypes, so the TypeScript also will check through.
(3) Remaining parameters
In JavaScript, if you define a function that can take any number of parameters, you can’t define each parameter list in turn. Before ES6 was released, you needed arguments to get the argument list. Arguments is a array-like object that contains all the actual arguments passed to the function when the function is called. It also contains a Length property that represents the number of arguments. Here’s how to simulate an overload of the implementation function:
function handleData() {
if (arguments.length === 1) return arguments[0] * 2;
else if (arguments.length === 2) return arguments[0] * arguments[1];
else return Array.prototype.slice.apply(arguments).join("_");
}
handleData(2); / / 4
handleData(2.3); / / 6
handleData(1.2.3.4.5); / / '1 _2_3_4_5'
Copy the code
If executed in TypeScript, three calls to handleData will return an error because the handleData function is defined without arguments.
In ES6, we added… Extend operator, which can take apart a function or object. It can also be used in function argument lists to handle any number of arguments:
const handleData = (arg1, ... args) = > {
console.log(args);
};
handleData(1.2.3.4.5); // [2, 3, 4, 5]
Copy the code
In TypeScript, you can specify types for the rest of the arguments:
const handleData = (arg1: number. args:number[]) = >{}; handleData(1."a"); // Parameters of type "string" cannot be assigned to parameters of type "number"
Copy the code
3. Function overload
As a dynamic language, JavaScript does not have function overloading, so it can only specify different processing logic in the function body by determining the number and type of parameters:
const handleData = x= > {
if (typeof x === 'string') {
return Number(x);
}
if (typeof x === 'number') {
return String(x);
}
return -1;
};
handleData(996) / / "996"
handleData("996") / / 996
handleData(null) // -1
Copy the code
Can see the incoming parameter types are different, the return value of the type is different, we introduced the three types of value as a parameter, the result can be concluded into string | number.
Is there a way to more precisely describe the type of function whose parameters are constrained by the return type? That’s function overloading. Some function overloading in TypeScript does not define several entity functions with the same name and then automatically call the corresponding functions based on the different number of arguments or types. It’s at the type system level for better type inference. TypeScript function overloading checks the return value of a function call by specifying multiple function type definitions:
const handleData = (x: string) :number;
const handleData = (x: number) :string;
const handleData = (x: null) :number;
const handleData = (x: string | number | null) :any= > {
if (typeof x === 'string') {
return Number(x);
}
if (typeof x === 'number') {
return String(x);
}
return -1;
};
handleData(996) / / "996"
handleData("996") / / 996
handleData(false) // error
Copy the code
First, we define three functions with the same name using the function keyword. These two functions have no actual function body logic, but only the function name, parameter and parameter types, and the return value type of the function. The fourth function of the same name, defined using function, is a complete entity function, containing the function name, parameters and parameter types, return value types, and function body. These four definitions make up a complete function with a type definition. The first two functions are called function overloads, while the fourth function is not an overload.
When the handleData function is called, TypeScript looks up the list of function overloads that match the input type and uses the first matching overload definition in preference. Therefore, we need to put the most precise function overloading first.
When this function is called and arguments are passed in, overloads that match the number and type of arguments are matched from the top down:
- The first call, passing in a number 996, from top to bottom matches the overload to match the second, so its return value should be a string, returning “996”;
- The second call, passing in a string “996”, matches the first one from top to bottom, so its return value should be numeric. Return to 996;
- The third call, passing a Boolean value false, does not match the overload, so an error is reported;
Note: Function overloading can only be defined using function, not interfaces or type aliases.