- Mastering TypeScript 3
- Nathan Rozentals
- 874字
- 2021-07-02 12:42:51
Class functions
All functions within a class adhere to the syntax and rules that we covered in the previous chapter on functions. As a refresher of these rules, all class functions can:
- Be strongly typed
- Use the any keyword to relax strong typing
- Have Optional parameters
- Have Default parameters
- Use argument arrays, or the rest parameter syntax
- Allow function callbacks and specify the function callback signature
- Allow function overloads
As an example of each of these rules, let's examine a class that has a number of different function signatures, and we will then discuss each one in detail, as follows:
class ComplexType implements IComplexType { id: number; name: string; constructor(idArg: number, nameArg: string); constructor(idArg: string, nameArg: string); constructor(idArg: any, nameArg: any) { this.id = idArg; this.name = nameArg; } print(): string { return "id:" + this.id + " name:" + this.name; } usingTheAnyKeyword(arg1: any): any { this.id = arg1; } usingOptionalParameters(optionalArg1?: number) { if (optionalArg1) { this.id = optionalArg1; } } usingDefaultParameters(defaultArg1: number = 0) { this.id = defaultArg1; } usingRestSyntax(...argArray: number []) { if (argArray.length > 0) { this.id = argArray[0]; } } usingFunctionCallbacks( callback: (id: number) => string ) { callback(this.id); } }
The first thing to note is the constructor function. Our class definition is using function overriding for the constructor function, allowing the class to be constructed using either a number and a string, or two strings. The following code shows how we would use each of these constructor definitions:
let ct_1 = new ComplexType(1, "ct_1"); let ct_2 = new ComplexType("abc", "ct_2"); let ct_3 = new ComplexType(true, "test");
The ct_1 variable uses the number, string variant of the constructor function, and the ct_2 variable uses the string, string variant. The ct_3 variable will generate a compile error, as we are not allowing a constructor to use a boolean, boolean variant. You may argue, however, that the last constructor function specifies an any, any variant and this should allow for our boolean, boolean usage. Just remember that constructor overloads follow the same rules as function overloads that we discussed in Chapter 2, Types, Variables, and Function Techniques, so this is not allowed.
We must be careful when using constructor overrides, however. Let's take a closer look at what happens when we call the string, string variant of the constructor:
let ct_2 = new ComplexType("abc", "ct_2"); ct_2.print();
Within the constructor function, we are assigning the value of the idArg argument to the id property on the class, as follows:
class ComplexType implements IComplexType { id: number; name: string; constructor(idArg: number, nameArg: string); constructor(idArg: string, nameArg: string); constructor(idArg: any, nameArg: any) { this.id = idArg; // careful - assigning a string to a number type this.name = nameArg; }
Even though we have defined the id property of the class to be of type number, when we call the constructor function with a string (because we have a constructor override), then the id property will actually be abc, which is clearly not a number type. TypeScript will not generate an error in this case, and will not automatically try to convert the value "abc" to a number. In cases where this type of functionality is required, we would need to use type guards to ensure type safety, as follows:
constructor(idArg: any, nameArg: any) { if (typeof idArg === "number") { this.id = idArg; } this.name = nameArg; }
Here, we have introduced a type guard to ensure that the id property (which is of type number) is only assigned if the idArg parameter is, in fact, a number.
Let's now take a look at the rest of the function definitions that we have defined for the class, starting with the usingTheAnyKeyword function:
ct_1.usingTheAnyKeyword(true); ct_1.usingTheAnyKeyword({ id: 1, name: "string"});
The first call in this sample is using a boolean value to call the usingTheAnyKeyword function, and the second is using an arbitrary object. Both of these function calls are valid, as the arg1 parameter is defined with the any type.
Next, there is the usingOptionalParameters function:
ct_1.usingOptionalParameters(1); ct_1.usingOptionalParameters();
Here, we are calling the usingOptionalParameters function firstly with a single argument, and then without any arguments. Again, these calls are valid, since the optionalArg1 argument is marked as optional.
And now for the usingDefaultParameters function:
ct_1.usingDefaultParameters(2); ct_1.usingDefaultParameters();
Both of these calls to the usingDefaultParameters function are valid. The first call will override the default value of 0, and the second call—without an argument—will use the default value of 0.
Next up is the usingRestSyntax function:
ct_1.usingRestSyntax(1,2,3); ct_2.usingRestSyntax(1,2,3,4,5);
Our rest function, usingRestSyntax, can be called with any number of arguments, as we are using the rest parameter syntax to hold these arguments in an array. Both of these calls are valid.
Finally, let's look at the usingFunctionCallbacks function:
function myCallbackFunction(id: number): string { return id.toString(); } ct_1.usingFunctionCallbacks(myCallbackFunction);
Here, we have defined a function named myCallbackFunction, which matches the callback signature required by the usingFunctionCallbacks function. This allows us to pass in myCallbackFunction as a parameter to the usingFunctionCallbacks function.
Note that, if you face any difficulty understanding these various function signatures, then please review the relevant sections in Chapter 2, Types, Variables, and Function Techniques, relating to functions, where each of these concepts is explained in detail.