Chapter 2: Types, Variables, and Function Techniques (TypeScript)
Tutorial

Chapter 2: Types, Variables, and Function Techniques (TypeScript)

T

Tim Editorial

20 Desember 202354 min
tags
Typescript
tutorial
javascript
id
2
published
Dec 20, 2023
reading_time
category
Tutorial
Programming
Documentation
author
TypeScript introduces strong typing to JavaScript through a simple syntax, referred to by Anders Hejlsberg as “syntactic sugar”. This “sugar” is what assigns a type to a variable, a function parameter, or even the return type of a function itself. As we discussed in Chapter 1, TypeScript – Tools and Framework Options, the benefits of strong typing include better error checking, the ability for an IDE to provide more intelligent code suggestions, and the ability to introduce object-oriented techniques into the JavaScript coding experience. There are a number of basic types that the language uses, such as number, string and boolean, to name a few. There are also rules by which the TypeScript compiler identifies what the type of a variable is. Understanding these rules and applying them to your code is a fundamental skill when writing TypeScript code. We will cover the following topics in this chapter:
  • Basic types and type syntax – strings, numbers, and booleans
  • Inferred typing and duck typing
  • Template strings
  • Arrays
  • Using for...in and for...of
  • The any type and explicit casting
  • Enums
  • Const enums and const values
  • The let keyword
  • Functions and anonymous functions
  • Optional and default function parameters
  • Argument arrays
  • Function callbacks, function signatures, and function overloads
  • Union types, type guards, and type aliases
Nb: This chapter is an introduction to the syntax used in the TypeScript language to apply strong typing to JavaScript. It is intended for readers who have not used TypeScript before, and covers the transition from standard JavaScript to TypeScript. If you already have experience with TypeScript, and have a good understanding of the topics listed here, then by all means have a quick read through, or skip to the next chapter.

Basic types

JavaScript variables can hold a number of data types, including numbers, strings, arrays, objects, functions, and more. The type of an object in JavaScript is determined by its assignment. This means that only at the point where we assign a value to a variable does the JavaScript runtime interpreter try to determine what the type of the particular variable is. While this may work in simple cases, the JavaScript runtime can also reassign the type of a variable depending on how it is being used, or on how it is interacting with other variables. It may assign a number to a string, for example, in certain cases. Let's take a look at an example of this dynamic typing in JavaScript, and what errors it can introduce, before exploring the strong typing that TypeScript uses, and its basic type system.

JavaScript typing

As we saw in Chapter 1, TypeScript – Tools and Framework Options, JavaScript objects and variables can be changed or reassigned on-the-fly. As an example of this, consider the following JavaScript code:
function doCalculation(a,b,c) { return (a * b) + c; } var result = doCalculation(2,3,1); console.log('doCalculation():' + result);
Here, we have a doCalculation function that is computing the product of the arguments a and b, and then adding the value of c. We are then calling the function with the arguments 2, 3 and 1, and logging the result to the console. The output of this sample would be:
doCalculation():7
This is the expected result, as 2 * 3 = 6, and 6 + 1 = 7. Now let's take a look at what happens if we inadvertently call the function with strings instead of numbers:
result = doCalculation("2","3","1"); console.log('doCalculation():' + result);
The output of this code sample is as follows:
doCalculation():61
The result of 61 is very different from our expected result of 7. So what is going on here? If we take a closer look at the code in the doCalculation function, we start to understand what JavaScript is doing with our variables, and their types. The product of two numbers, that is, (a * b), returns a numeric value, so JavaScript is automatically converting the values "2" and "3" to numbers in order to compute the product, and correctly computing the value 6. This is a particular rule that JavaScript applies in order to convert strings to numbers, when the result should be a number. But the addition symbol, that is, +, does not infer that both values are numeric. Because the argument c is a string, JavaScript is converting the value 6 into a string in order to add two strings. This results in the string "6" being added to the string "1", which results in the value "61". Obviously, these sorts of automatic type conversions can cause unwanted behavior in our code.

TypeScript typing

TypeScript, on the other hand, is a strongly typed language. Once you have declared a variable to be of type string, you can only assign string values to it. All further code that uses this variable must treat it as though it has a type of string. This helps to ensure that code that we write will behave as expected. JavaScript programmers have always relied heavily on documentation to understand how to call functions, and the order and type of the correct function parameters. But what if we could take all of this documentation and include it within the IDE? Then, as we write our code, our compiler could point out to us automatically that we were using variables in the wrong way. Surely this would make us more efficient, more productive programmers, allowing us to generate code with fewer errors? TypeScript does exactly that. It introduces a very simple syntax to define the type of a variable to ensure that we are using it in the correct manner. If we break any of these rules, the TypeScript compiler will automatically generate errors, pointing us to the lines of code that are in error. This is how TypeScript got its name. It is JavaScript with strong typing, hence TypeScript. Let's take a look at this very simple language syntax that enables the Type in TypeScript.

Type syntax

The TypeScript syntax for declaring the type of a variable is to include a colon (:), after the variable name, and then indicate its type. Let's rewrite our problematic doCalculation function to only accept numbers. Consider the following TypeScript code:
function doCalculation( a : number, b : number, c : number) { return ( a * b ) + c; } var result = doCalculation(3,2,1); console.log("doCalculation():" + result);
Here, we have specified that the doCalculation function needs to be invoked with three numbers. Again, the TypeScript syntax for declaring a type is to include a colon, and then the variable type, hence : number for the properties a, b, and c. If we now attempt to call this function with strings, as we did with the JavaScript sample, as follows:
var result = doCalculation("1", "2", "3"); console.log("doCalculation():" + result);
The TypeScript compiler will generate the following error:
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
This error message clearly tells us that we cannot assign a string where a numeric value is expected. To further illustrate this point, consider the following TypeScript code:
var myString : string; var myNumber : number; var myBoolean : boolean; myString = "1"; myNumber = 1; myBoolean = true;
Here, we are telling the compiler that the myString variable is of type string, even before the variable itself has been used. Similarly, the myNumber variable is of type number, and the myBoolean variable is of type boolean. TypeScript has introduced the string, number, and boolean keywords for each of these basic JavaScript types. If we then attempt to assign a value to a variable that is not of the same type, the TypeScript compiler will generate a compile-time error. Given the variables declared in the preceding code, consider the following TypeScript code:
myString = myNumber; myBoolean = myString; myNumber = myBoolean;
notion image
The TypeScript compiler is now generating compile errors because it has detected that we are attempting to mix these basic types. The first error is generated because we cannot assign a number value to a variable of type string. Similarly, the second compile error indicates that we cannot assign a string value to a variable of type boolean. Again, the third error is generated because we cannot assign a boolean value to a variable of type number. The strong typing syntax that the TypeScript language introduces means that we need to ensure that the types on the left-hand side of an assignment operator (=) are the same as the types on the right-hand side of the assignment operator. To fix the preceding TypeScript code, and remove the compile errors, we would need to do something similar to the following:
myString = myNumber.toString(); myBoolean = (myString === "test"); if (myBoolean) { myNumber = 1; }
Our first line of code has been changed to call the .toString() function on the myNumber variable (which is of type number), in order to return a value that is of type string.This line of code, then, does not generate a compile error because both sides of the equal sign (or assignment operator) are strings. Our second line of code has also been changed so that the right-hand side of the assignment operator returns the result of a comparison, myString === "test", which will return a value of type boolean. The compiler will therefore allow this code, because both sides of the assignment resolve to a value of type boolean. The last line of our code snippet has been changed to only assign the value 1 (which is of type number) to the myNumber variable, if the value of the myBoolean variable is true. Anders Hejlsberg describes this feature as “syntactic sugar”. In other words, with a little “sugar” on top of comparable JavaScript code, TypeScript has enabled our code to transform into a strongly typed language. Whenever you break these strong typing rules, the compiler will generate errors for your offending code.

Inferred typing

TypeScript also uses a technique called inferred typing to determine the type of a variable. In other words, TypeScript will infer the type of a variable based on its first usage, and then assume the same type for this variable in the rest of your code block. As an example of this, consider the following TypeScript code:
var inferredString = "this is a string"; var inferredNumber = 1; inferredString = inferredNumber;
We start by declaring a variable named inferredString, and assign a string value to it. TypeScript identifies that this variable has been assigned a value of type string, and will, therefore, infer any further usage of this variable to be of type string. Our second variable, named inferredNumber, has a number assigned to it. Again, TypeScript is inferring the type of this variable to be of type number. If we then attempt to assign the inferredString variable (of type string) to the inferredNumber variable (of type number) in the last line of code, TypeScript will generate a familiar error message:
error TS2011: Build: Cannot convert 'string' to 'number' This error is generated because of TypeScript's inferred typing rules.
Remember that if we do not explicitly specify the type of a variable by using the colon ( : type ) syntax, then TypeScript will automatically infer the type of a variable based on its first assignment.

Duck typing

TypeScript also uses a method called duck typing for more complex variable types. Duck typing means that if it looks like a duck, and quacks like a duck, then it probably is a duck. Consider the following TypeScript code:
var complexType = { name: "myName", id: 1 }; complexType = { id: 2, name: "anotherName" };
We start with a variable named complexType that has been assigned a simple JavaScript object with a name and an id property. On our second line of code, we are reassigning the value of this complexType variable to another object that also has an id and a name property. The compiler will use duck typing in this instance to figure out whether this assignment is valid. In other words, if an object has the same set of properties as another object, then they are considered to be of the same type. To further illustrate this point, let's see how the compiler reacts if we attempt to assign an object to our complexType variable that does not conform to this duck typing:
var complexType = { name: "myName", id: 1 }; complexType = { id: 2 };
The first line of this code snippet defines our complexType variable, and assigns to it an object that contains both an id and a name property. From this point on, TypeScript will use this inferred type on any value we attempt to assign to the complexType variable. On our second line of code, we are attempting to reassign the complexType variable to a value that has an id property but not a name property. This line of code will generate the following compilation error:
error TS2322: Type '{ id: number; }' is not assignable to type'{ name: string; id: number; }'. Property 'name' is missing in type '{ id: number; }'.
The error message is pretty self-explanatory. In this instance, TypeScript is using duck typing to ensure type safety. As the complexType variable has both an id and a name property, any value that is assigned to it must also have both an id and a name property. Note that the following code will also generate an error message:
var complexType = { name: "myName", id: 1 }; complexType = { name : "extraproperty", id : 2, extraProp: true };
The error generated here is as follows:
error TS2322: Type '{ name: string; id: number; extraProp: boolean; }' is not assignable to type '{ name: string; id: number; }'. Object literal may only specify known properties, and 'extraProp' does not exist in type '{ name: string; id: number; }'.
As can be seen in this error message, the variable complexType does not have an extraProp property, and therefore the assignment fails. Inferred typing and duck typing are powerful features of the TypeScript language, bringing strong typing to our code without the need to use explicit typing.

Template strings

Before we continue our discussion on types, it is worth noting that TypeScript allows for ES6 template string syntax. This syntax provides a convenient method for injecting values into strings. Consider the following code:
var myVariable = "test"; console.log("myVariable=" + myVariable);
Here, we are simply assigning a value to a variable, and logging the result to the console, with a little bit of formatting to make the message readable. Note how we are concatenating the strings with the "string" + variable syntax. Let's now take a look at the equivalent TypeScript code:
var myVariable = "test"; console.log(`myVariable=${myVariable}`);
On the second line of this code snippet, we have introduced the ES6 template string syntax for easier manipulation of strings. There are two important things to note about this syntax. Firstly, we have switched the string definition from a double quote (") to an apostrophe (`). Using an apostrophe signals to the TypeScript compiler that it should look for template values within the string enclosed by the apostrophes, and replace them with actual values. Secondly, we have used a special ${ ... } syntax within the string to denote a template. TypeScript will inject the value of any variable that is currently in scope into the string for us. This is a convenient method of dealing with strings.
Nb: The TypeScript compiler will parse this ES6 style of string templates, and generate JavaScript code that uses standard string concatenation. In this way, the ES6 string template syntax can be used no matter what JavaScript version is being targeted.
In the remainder of this chapter, we will use this string template syntax

Arrays

Besides the base JavaScript types of string, number, and boolean, TypeScript has two other basic data types that we will now take a closer look at – arrays and enums. Let's look at the syntax for defining arrays. An array is simply marked with the [] notation, similar to JavaScript, and each array can be strongly typed to hold a specific type, as seen in the code below:
var arrayOfNumbers: number [] = [1,2,3]; arrayOfNumbers = [3,4,5,6,7,8,9]; console.log(`arrayOfNumbers: ${arrayOfNumbers}`); arrayOfNumbers = ["1", "2", "3"];
Here, we start by defining an array named arrayOfNumbers, and further specifying that each element of this array must be of type number. We then reassign this array to hold some different numerical values. Note that we can assign any number of elements to an array. We then use a simple template string to print the array to the console. The last line of this snippet, however, will generate the following error message:
hello_ch02.ts(51,1): error TS2322: Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'.
This error message is warning us that the variable arrayOfNumbers is strongly typed to only accept values of type number. As our code is trying to assign an array of strings to this array of numbers, an error is generated. The output of this code snippet is as follows:
arrayOfNumbers: 3,4,5,6,7,8,9

for...in and for...of

When working with arrays, it is common practice to loop through array items in order to perform some task. This is generally accomplished within a for loop by manipulating an array index, as shown in the following code:
var arrayOfStrings : string[] = ["first", "second", "third"]; for( var i = 0; i < arrayOfStrings.length; i++ ) { console.log(`arrayOfStrings[${i}] = ${arrayOfStrings[i]}`); }
Here, we have an array named arrayOfStrings, and a standard for loop that is using the variable i as an index into our array. We access the array item using the syntax arrayOfStrings[i]. The output of this code is as follows:
arrayOfStrings[0] = first arrayOfStrings[1] = second arrayOfStrings[2] = third
TypeScript introduces the for...in syntax to simplify looping through arrays. Here is an example of the above for loop expressed using this new syntax:
for( var itemKey in arrayOfStrings) { var itemValue = arrayOfStrings[itemKey]; console.log(`arrayOfStrings[${itemKey}] = ${itemValue}`); }
Here, we have simplified the for loop by using the itemKey in arrayOfStrings syntax. Note that the value of the variable itemKey will iterate through the keys of the array, and not the array elements themselves. Within the for loop, we are first de-referencing the array to extract the array value for this itemKey, and then logging both the itemKey and the itemValue to the console. The output of this code is as follows:
arrayOfStrings[0] = first arrayOfStrings[1] = second arrayOfStrings[2] = third
If we do not necessarily need to know the keys of the array, and are simply interested in the values held within the array, we can further simplify looping through arrays using the for...of syntax. Consider the following code:
for( var arrayItem of arrayOfStrings ) { console.log(`arrayItem = ${arrayItem} `); }
Here, we are using the for...of syntax to iterate over each value of the arrayOfStrings array. Each time that the for loop is executed, the arrayItem variable will hold the next element in the array. The output of this code is as follows:
arrayItem = first arrayItem = second arrayItem = third

The any type

All this type checking is well and good, but JavaScript is flexible enough to allow variables to be mixed and matched. The following code snippet is actually valid JavaScript code:
var item1 = { id: 1, name: "item 1" }; item1 = { id: 2 };
Here, we assign an object with an id property and a name property to the variable item1. We then reassign this variable to an object that has an id property but not a name property. Unfortunately, as we have seen previously, this is not valid TypeScript code, and will generate the following error:
hello_ch02.ts(130,1): error TS2322: Type '{ id: number; }' is not assignable to type '{ id: number; name: string; }'. Property 'name' is missing in type '{ id: number; }'.
TypeScript introduces the any type for such occasions. Specifying that an object has a type of any, in essence, relaxes the compiler's strict type checking. The following code shows how to use the any type:
var item1 : any = { id: 1, name: "item 1" }; item1 = { id: 2 };
Note how our first line of code has changed. We specify the type of the variable item1 to be of type : any. This special TypeScript keyword then allows a variable to follow JavaScript's loosely defined type rules, so that anything can be assigned to anything. Without the type specifier of : any, the second line of code would normally generate an error.

Explicit casting

As with any strongly typed language, there comes a time where you need to explicitly specify the type of an object. This concept will be expanded upon more thoroughly in the next chapter, but it is worthwhile to make a quick note of explicit casting here. An object can be cast to the type of another by using the < > syntax. This is not a cast in the strictest sense of the word; it is more of an assertion that is used at runtime by the TypeScript compiler. Any explicit casting that you use will be compiled away in the resultant JavaScript and will not affect the code at runtime. Let's take a look at an example that uses explicit casting, as follows:
var item1 = <any>{ id: 1, name: "item 1" }; item1 = { id: 2 };
Here, have now replaced the : any type specifier on the left-hand side of the assignment, with an explicit cast of <any> on the right-hand side. This tells the compiler to explicitly treat the { id: 1, name: "item 1" } object on the right-hand side of the assignment operator as a type of any. So the item1 variable on the left-hand side of the assignment, therefore, also has the type of any (due to TypeScript's inferred typing rules).
This then allows us to assign an object with only the { id: 2 } property to the variable item1 on the second line of code. This technique of using the < > syntax on the right-hand side of an assignment is called explicit casting. While the any type is a necessary feature of the TypeScript language and is used for backward compatibility with JavaScript, its usage should really be limited as much as possible. As we have seen with untyped JavaScript, over-use of the any type will quickly lead to coding errors that will be difficult to find. Rather than using the type any, try to figure out the correct type of the object you are using, and then use this type instead. We use an acronym within our programming teams–Simply Find an Interface for the Any Type (S.F.I.A.T) pronounced as sviat or sveat. While this may sound silly, it brings home the point that the any type should always be replaced with an interface, so simply find it. An interface is a way of defining custom types in TypeScript, which we will cover in the next chapter. Just remember that, by actively trying to define what an object's type should be, we are building strongly typed code, and therefore, protecting ourselves from future coding errors and bugs.

Enums

Enums are a special type borrowed from other languages such as C#, C++, and Java, and provides a solution to the problem of special numbers. An enum associates a human-readable name for a specific number. Consider the following code:
enum DoorState { Open, Closed, Ajar }
Here, we have defined an enum called DoorState to represent the state of a door. Valid values for this door state are Open, Closed, or Ajar. Under the hood (in the generated JavaScript), TypeScript will assign a numeric value to each of these human-readable enum values. In this example, the DoorState.Open enum value will equate to a numeric value of 0. Likewise, the enum value DoorState.Closed will equate to the numeric value of 1, and the DoorState.Ajar enum value will equate to 2. Let's take a quick look at how we would use these enum values:
var openDoor = DoorState.Open; console.log(`openDoor is: ${openDoor}`);
The first line of this snippet creates a variable named openDoor, and sets its value to DoorState.Open. The second line simply logs the value of openDoor to the console. The output of this would be: openDoor is: 0 This clearly shows that the TypeScript compiler has replaced the enum value of DoorState.Open with the numeric value 0. Now let's use this enum in a slightly different way:
var closedDoor = DoorState["Closed"]; console.log(`closedDoor is : ${closedDoor}`);
This code snippet uses a string value of "Closed" to lookup the enum type, and assigns the resulting enum value to the closedDoor variable. The output of this code would be: closedDoor is : 1
This sample clearly shows that the enum value of DoorState.Closed is the same as the enum value of DoorState["Closed"], because both variants resolve to the numeric value of 1. Finally, let's take a look at what happens when we reference an enum using an array type syntax:
var ajarDoor = DoorState[2]; console.log(`ajarDoor is : ${ajarDoor}`);
Here, we assign the variable ajarDoor to an enum value based on the second index value of the DoorState enum. The output of this code, though, is surprising:
ajarDoor is : Ajar You may have been expecting the output to be simply 2, but here we are getting the string "Ajar", which is a string representation of our original enum name. This is actually a neat little trick allowing us to access a string representation of our enum value. The reason that this is possible is down to the JavaScript that has been generated by the TypeScript compiler. Let's take a look, then, at the closure that the TypeScript compiler has generated:
var DoorState; (function (DoorState) { DoorState[DoorState["Open"] = 0] = "Open"; DoorState[DoorState["Closed"] = 1] = "Closed"; DoorState[DoorState["Ajar"] = 2] = "Ajar"; })(DoorState || (DoorState = {}));
This strange-looking syntax is building an object that has a specific internal structure. It is this internal structure that allows us to use this enum in the various ways that we have just explored. If we interrogate this structure while debugging our JavaScript, we will see that the internal structure of the DoorState object is as follows:
DoorState {...} [prototype]: {...} [0]: "Open" [1]: "Closed" [2]: "Ajar" [prototype]: [] Ajar: 2 Closed: 1 Open: 0
The DoorState object has a property called "0", which has a string value of "Open". Unfortunately, in JavaScript the number 0 is not a valid property name, so we cannot access this property by simply using DoorState.0. Instead, we must access this property using either DoorState[0] or DoorState["0"]. The DoorState object also has a property named Open, which is set to the numeric value 0. The word Open is a valid property name in JavaScript, so we can access this property using DoorState["Open"], or simply DoorState.Open, which equates to the same property in JavaScript. While the underlying JavaScript can be a little confusing, all we need to remember about enums is that they are a handy way of defining an easily remembered, human-readable name to a special number. Using human-readable enums, instead of just scattering various special numbers around in our code, makes the intent of the code clearer. Using an application-wide value named DoorState.Open or DoorState.Closed is far simpler than remembering to set a value to 0 for Open, 1 for Closed, and 3 for Ajar. As well as making our code more readable and more maintainable, using enums also protects our code base whenever these special numeric values change because they are all defined in one place. One last note on enums, is that we can set the numeric value manually, if required:
enum DoorState { Open = 3, Closed = 7, Ajar = 10 }
Here, we have overridden the default values of the enum to set DoorState.Open to 3, DoorState.Closed to 7, and DoorState.Ajar to 10.

Const enums

A slight variant of the enum type is the const enum, which simply adds the keyword const before the enum definition, as follows:
const enum DoorStateConst { Open, Closed, Ajar } var constDoorOpen = DoorStateConst.Open; console.log(`constDoorOpen is : ${constDoorOpen}`);
const enums have been introduced largely for performance reasons, and the resultant JavaScript will not contain the full closure definition for the DoorStateConst enum as we saw previously. Let's take a quick look at the JavaScript that is generated from this DoorStateConst enum: var constDoorOpen = 0 /* Open */; Note how we do not have a full JavaScript closure for the DoorStateConst at all. The compiler has simply resolved the DoorStateConst.Open enum to its internal value of 0, and removed the const enum definition entirely. With const enums, we therefore cannot reference the internal string value of an enum, as we did in our previous code sample. If we try to reference a const enum using the array syntax, as follows:
console.log(${DoorStateConst[0]});
We get the following error message:
error TS2476: A const enum member can only be accessed using a string literal.
We can, however, still use the string property accessor on a const enum, as follows: console.log(${DoorStateConst["Open"]}); When using const enums, just keep in mind that the compiler will strip away all enum definitions and simply substitute the numeric value of the enum directly into our JavaScript code.

Const values

The TypeScript language also allows us to define a variable as a constant, by using the const keyword. If a variable has been marked as const, then its value can only be set when the variable is defined, and cannot be changed afterwards. Consider the following code:
const constValue = "test"; constValue = "updated";
Here, we have defined a variable named constValue, and indicated that it cannot be changed by using the const keyword. Attempting to compile this code will result in the following compile error:
error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property.

The let keyword

Variables in JavaScript are defined by using the keyword var. The JavaScript runtime is very lenient, however, when it comes to variable definitions. If the JavaScript runtime comes across a variable that has not been previously defined or given a value, then the value for this variable will be undefined. Consider the following code snippet:
console.log(`anyValue = ${anyValue}`); var anyValue = 2; console.log(`anyValue = ${anyValue}`);
Here, we start by logging the value of a variable named anyValue to the console. Note, however, that the variable anyValue is only defined on the second line of this code snippet. In other words, we can use a variable in JavaScript before it is defined. The output of this code is as follows:
anyValue = undefined anyValue = 2
The semantics of using the var keyword presents us with a small problem. Using the var keyword does not check to see whether the variable itself has been defined before we actually use it. This could obviously lead to unwanted behavior, as the value of an undefined or unallocated variable is always undefined. TypeScript introduces the let keyword, which can be used in the place of the var keyword when defining variables. One of the advantages of using the let keyword is that we cannot use a variable name before it has been defined. Consider the following code:
console.log(`letValue = ${lValue}`); let lValue = 2;
Here, we are attempting to log the value of the variable lValue to the console even before it has been defined, similar to how we were using the anyValue variable earlier. However, when using the let keyword instead of the var keyword, this code will generate an error, as follows:
error TS2448: Block-scoped variable 'lValue' used before its declaration.
Here, the TypeScript compiler generates an error if we attempt to use a variable before it is defined. To fix this code, then, we need to define our variable lValue before it is first used, as follows:
let lValue = 2; console.log(`lValue = ${lValue}`);
This code will compile correctly, and output the following to the console: lValue = 2 Another side-effect of using the let keyword, is that variables defined with let are block-scoped. This means that their value and definition are limited to the block of code that they reside in. As an example of this, consider the following code:
let lValue = 2; console.log(`lValue = ${lValue}`); if (lValue == 2) { let lValue = 2001; console.log(`block scoped lValue : ${lValue} `); } console.log(`lValue = ${lValue}`);
Here, we define the lValue variable on the first line using the let keyword, and assign a value of 2 to it. We then log the value of lValue to the console. On the first line within the if statement, note how we are redefining a variable named lValue to hold the value 2001. We are then logging the value of lValue to the console (within the if statement block).
The last line of this code snippet again logs the value of the lValue variable to the console, but this time lValue is outside the if statement block-scope. The output of this code is as follows:
lValue = 2 block scoped lValue : 2001 lValue = 2
What these results are showing us is that let variables are confined to the scope in which they are defined. In other words, the let lValue = 2001; statement defines a new variable that will only be visible inside the if statement block of code. As it is a new variable, it will also not influence the value of the lValue variable that is outside its scope. This is why the value of lValue is 2 both before and after the if statement block, and 2001 within it. The let statement, therefore, provides us with a safer way of declaring variables, and limiting their validity to the current scope.

Functions

So far, we have seen how to add type annotations to variables, and have also seen how this syntax is easily extended to function parameters. There are, however, a few more typing rules that TypeScript uses when it comes to functions.

Function return types

Using the very simple “syntactic sugar” TypeScript syntax, we can also define the type of avariable that a function should return. In other words, when we call a function, and it returns a value, what type should the value be treated as ?
Consider the following TypeScript code:
function addNumbers(a: number, b: number) : string { return a + b; } var addResult = addNumbers(2,3); console.log(`addNumbers returned : ${addResult}`);
Here, we have added a :number type to both of the parameters of the addNumbers function (a and b), and we have also added a :string type just after the ( ) braces. Placing a type annotation after the function definition means that we are defining the return type of the entire function. In our example, then, the return type of the function addNumbers must be of type string. Unfortunately, this code will generate an error message as follows: error TS2322: Type 'number' is not assignable to type 'string'. What this error message is telling us is that the return type of the addNumbers function must be a string. Unfortunately, the function itself is returning a number, and not a string –hence the error. Taking a closer look at the code, we note that the offending code is, in fact,
return a + b. As a and b are numbers, we are returning the result of adding two numbers, which is of type number. To fix this code, then, we need to ensure that the function returns a string, as follows:
function addNumbers(a: number, b: number) : string return (a + b).toString(); }
This code will now compile correctly, and will output: addNumbers returned : 5

Anonymous functions

The JavaScript language also has the concept of anonymous functions. These are functions that are defined on the fly and don't specify a function name. Consider the following JavaScript code:
var addVar = function(a,b) { return a + b; } var addVarResult = addVar(2,3); console.log("addVarResult:" + addVarResult);
This code snippet defines a function that has no name and adds two values. Because the function does not have a name, it is known as an anonymous function. This anonymous function is then assigned to a variable named addVar. The addVar variable can then be invoked as a function with two parameters, and the return value will be the result of executing the anonymous function. The output of this code will be:
addVarResult:5
Let's now rewrite the preceding anonymous JavaScript function in TypeScript, as follows:
var addFunction = function(a:number, b:number) : number { return a + b; } var addFunctionResult = addFunction(2,3); console.log(`addFunctionResult : ${addFunctionResult}`);
Here, we see that TypeScript allows anonymous functions in the same way that JavaScript does, but also allows standard type annotations. The output of this TypeScript code is as follows: addFunctionResult : 5

Optional parameters

When we call a JavaScript function that is expecting parameters, and we do not supply these parameters, then the value of the parameter within the function will be undefined. As an example of this, consider the following JavaScript code:
var concatStrings = function(a,b,c) { return a + b + c; } var concatAbc = concatStrings("a", "b", "c"); console.log("concatAbc :" + concatAbc); var concatAb = concatStrings("a", "b"); console.log("concatAb :" + concatAb);
The output of this code is as follows:
concatAbc :abc
concatAb :abundefined
Here, we have defined a function called concatStrings that takes three parameters, a, b, and c, and simply returns the sum of these values. We are then calling this function with three arguments, and assigning the result to the variable concatAbc. As can be seen from the output, this returns the string "abc". If, however, we only supply two arguments, as seen with the usage of the variable concatAb, the function returns the string "abundefined". In JavaScript, if we call a function and do not supply a parameter, then the missing parameter will be undefined, which in this case is the parameter c. TypeScript introduces the question mark ? syntax to indicate optional parameters. This allows us to mimic the JavaScript calling syntax where we can call the same function with some missing arguments. As an example of this, consider the following TypeScript code:
function concatStrings( a: string, b: string, c?: string) { return a + b + c; } var concat3strings = concatStrings("a", "b", "c"); console.log(`concat3strings : ${concat3strings}`); var concat2strings = concatStrings("a", "b"); console.log(`concat2strings : ${concat2strings}`); var concat1string = concatStrings("a");
This is a strongly typed version of the original concatStrings JavaScript function that we were using previously. Note the addition of the ? character in the syntax for the third parameter: c?: string. This indicates that the third parameter is optional, and therefore, all of the above code will compile cleanly, except for the last line. The last line will generate an error:
error TS2081: Build: Supplied parameters do notmatch any signature of call target.
This error is generated because we are attempting to call the concatStrings function with only a single parameter. Our function definition, though, requires at least two parameters, with only the third parameter being optional.
Any optional parameters must be the last parameters defined in the function definition. You can have as many optional parameters as you want, as long as non-optional parameters precede the optional parameters.

Default parameters

A subtle variant of the optional parameter syntax allows us to specify the default value of a parameter. If an optional parameter value is not supplied, we can specify what the default value of this optional parameter should be. Let's modify our preceding function definition to use an optional parameter with a default value, as follows:
function concatStringsDefault( a: string, b: string, c: string = "c") { return a + b + c; } var defaultConcat = concatStringsDefault("a", "b"); console.log(`defaultConcat : ${defaultConcat}`);
This function definition has now dropped the ? optional parameter syntax, but instead has assigned a value of "c" to the last parameter: c:string = "c". By using default parameters, if we do not supply a value for the final parameter named c, the concatStringsDefault function will substitute the default value of "c" instead. The argument c, therefore, will not be undefined. The output of this code will therefore be:
defaultConcat : abc
Note that using the default parameter syntax will automatically make the parameter that has a default value optional.

Rest parameters

The JavaScript language also allows a function to be called with a variable number of arguments. Every JavaScript function has access to a special variable, named arguments, that can be used to retrieve all arguments that have been passed into the function. As an example of this, consider the following JavaScript code:
function testArguments() { if (arguments.length > 0) { for (var i = 0; i < arguments.length; i++ ) { console.log("argument[" + i + "] = " + arguments[i]); } } } testArguments(1,2,3); testArguments("firstArg");
Here, we have defined a function, named testArguments, which does not have any named parameters. Note, though, that we can use the special variable, named arguments, to test whether the function was called with any arguments. In our sample, we simply loop through the arguments array, and log the value of each argument to the console, by using an array index – arguments[i]. The output of this code is as follows:
argument[0] = 1
argument[1] = 2
argument[2] = 3
argument[0] = firstArg
In order to express the equivalent function definition in TypeScript, we will need to use a syntax that is known as the rest parameter syntax. Rest parameters use the TypeScript syntax of three dots (...) in the function declaration to express a variable number of function parameters. Here is the equivalent testArguments function, expressed in TypeScript:
function testArguments(... argArray: number []) { if (argArray.length > 0) { for (var i = 0; i < argArray.length; i++) { console.log(`argArray[${i}] = ${argArray[i]}`); // use JavaScript arguments variable console.log(`arguments[${i}] = ${arguments[i]}`) } } } testArguments(9); testArguments(1,2,3);
Note the use of the ...argArray: number[] syntax for our testArguments function parameters. This syntax is telling the TypeScript compiler that the function can accept any number of arguments. We can therefore call this function, as seen in the last two lines of the preceding code, with any number of arguments. There are also two console.log statements in this for loop. The first uses argArray[i], and the second uses the standard JavaScript arguments variable, arguments[i] The output of this code is as follows:
argArray[0] = 9 arguments[0] = 9 argArray[0] = 1 arguments[0] = 1 argArray[1] = 2 arguments[1] = 2 argArray[2] = 3 arguments[2] = 3
The subtle difference between using argArray and arguments is the inferred type of the argument. Since we have explicitly specified that argArray is of type number, TypeScript will treat any item of the argArray array as a number. However, the internal arguments array does not have an inferred type, and so will be treated as the any type.
We can also combine normal parameters along with rest parameters in a function definition, as long as the rest parameters are the last to be defined in the parameter list, as follows:
function testNormalAndRestArguments( arg1: string, arg2, number, ...argArray: number[] ) { }
Here, we have two normal parameters named arg1 and arg2 and then an argArray rest parameter. Mistakenly placing the rest parameter at the beginning of the parameter list will generate a compile error.

Function callbacks

One of the most powerful features of JavaScript – and in fact the technology that Node was built on – is the concept of callback functions. A callback function is a function that is passed into another function, and is then generally invoked inside the function. Remember that JavaScript is not strongly typed, so we can declare a variable to be either a value, or a function. Therefore, just as we can pass a value into a function, we can also pass a function into a function. This is best illustrated by taking a look at some sample JavaScript code:
var callbackFunction = function(text) { console.log('inside callbackFunction ' + text); } function doSomethingWithACallback( initialText, callback ) { console.log('inside doSomethingWithCallback ' + initialText); callback(initialText); } doSomethingWithACallback('myText', callbackFunction);
Here, we start with a variable named callbackFunction, which is a function that takes a single parameter. This callbackFunction simply logs the text argument to the console. We then define a function named doSomethingWithACallback that takes two parameters – initialText and callback. The first line of this function simply logs "inside doSomethingWithACallback" to the console. The second line of the doSomethingWithACallback is the interesting bit. It assumes that the callback argument is in fact a function, and invokes it, passing in the initialText variable. If we run this code, we will get two messages logged to the console, as follows:
inside doSomethingWithCallback myText
inside callbackFunction myText
But what happens if we do not pass a function as a callback? There is nothing in the above code that signals to us that the second parameter of doSomethingWithACallback must be a function. If we inadvertently called the doSomethingWithACallback function with two strings, as shown below:
doSomethingWithACallback('myText', 'anotherText');
We would get a JavaScript runtime error:
TypeError: callback is not a function
Defensively minded programmers, however, would first check whether the callback parameter was in fact a function before invoking it, as follows:
function doSomethingWithACallback( initialText, callback ) { console.log('inside doSomethingWithCallback ' + initialText); if (typeof callback === "function") { callback(initialText); } else { console.log(initialText + ' is not a function !!') } } doSomethingWithACallback('myText', 'anotherText');
Note the third line of this code snippet, where we check the type of the callback variable before invoking it. If it is not a function, we then log a message to the console. The output of the code snippet would be:
inside doSomethingWithCallback myText
anotherText is not a function !!
JavaScript programmers, therefore, need to be careful when working with callbacks. Firstly, they need to code around the invalid use of callback functions, and secondly, they need to document and understand which parameters are, in fact, callbacks. What if we could document our JavaScript callback functions in our code, and then warn users when they are not passing a function when one is expected?

Function signatures

The TypeScript “syntactic sugar” that enforces strong typing on normal variables, can also be used with callback functions. In order to do this, TypeScript introduces a new syntax, named the fat arrow syntax, () =>. When the fat arrow syntax is used, it means that one of the parameters to a function needs to be another function. Let's take a closer look at what this means. We will rewrite our previous JavaScript callback sample in TypeScript, as follows:
function callbackFunction(text: string) { console.log(`inside callbackFunction ${text}`); }
We start with the initial callback function, which takes a single text parameter and logs a message to the console when this function is called. We can then define the doSomethingWithACallback function, as follows:
function doSomethingWithACallback( initialText: string, callback : (initialText: string) => void ) { console.log(`inside doSomethingWithCallback ${initialText}`); callback(initialText);
Here, we have defined our doSomethingWithACallback function with two parameters. The first parameter is initialText, and is of type string. The second parameter is named callback, and now uses the fat arrow syntax. Let's take a look at this syntax in a little more detail: callback: (initialText: string) => void
The callback argument used here is typed (by the : syntax) to be a function, by using the fat arrow syntax () =>. Additionally, this function takes a parameter named initialText that is of type string. To the right of the fat arrow syntax, we can see a new TypeScript basic type, called void. Void is a keyword to denote that a function does not return a value. So, the doSomethingWithACallback function will only accept, as its second argument, a function that takes a single string parameter and returns void.
We can then use this function as follows:
doSomethingWithACallback("myText", callbackFunction);
This code snippet is the same as was used in our JavaScript sample earlier. TypeScript will check the type of the callbackFunction parameter that was passed in, and ensure that it is, in fact, a function that accepts a single string as an argument and does not return anything. If we try to invoke this doSomethingWithACallback incorrectly:
doSomethingWithACallback("myText", "this is not a function");
The compiler will generate the following message:
error TS2345: Argument of type 'string' is not assignable to parameter of type '(initialText: string) => void'.
This error message is clearly stating that the second argument, that is, "this is not a function", is of type string, where a parameter that is a function of type (initialText: string) => void is expected. Given this function signature for the callback parameter, the following code would also generate compile-time errors:
function callbackFunctionWithNumber(arg1: number) { console.log(`inside callbackFunctionWithNumber ${arg1}`) } doSomethingWithACallback("myText", callbackFunctionWithNumber);
Here, we are defining a function named callBackFunctionWithNumber, which takes a number as its only parameter. When we attempt to compile this code, we will get an error message indicating that the callback parameter, which is now our callBackFunctionWithNumber function, also does not have the correct function signature, as follows:
error TS2345: Argument of type '(arg1: number) => void' is not assignable to parameter of type '(initialText: string) => void'.
This error message is clearly stating that a parameter of type (initialText: string) => void is expected, but an argument of type (arg1: number) => void was used instead.
In function signatures, the parameter name (arg1 or initialText) does not need to be the same. Only the number of parameters, their types, and the return type of the function need to be the same.
This is a very powerful feature of TypeScript – defining in code what the signatures of functions should be, and warning users when they do not call a function with the correct parameters. As we saw in our introduction to TypeScript, this is most significant when we are working with third-party libraries. Before we are able to use third-party functions, classes, or objects in TypeScript, we need to define what their function signatures are. These function definitions are put into a special type of TypeScript file, called a declaration file, and saved with a .d.ts extension. We will take an in-depth look at declaration files in Chapter 4, Decorators, Generics, and Asynchronous Features

Function overloads

As JavaScript is a dynamic language, we can often call the same function with different argument types. Consider the following JavaScript code:
function add(x,y) { return x + y; } console.log('add(1,1)=' + add(1,1)); console.log('add("1","1")=' + add("1","1"));
Here, we are defining a simple add function that returns the sum of its two parameters, x and y. The last two lines of the preceding code snippet simply log the result of the add function with different types – two numbers and two strings. If we run the preceding code, we will see the following output:
add(1,1)=2 add("1","1")=11
In order to reproduce this ability to call the same function with different types, TypeScript introduces a specific syntax, called function overloads. If we were to replicate the above code in TypeScript, we would need to use this function overload syntax, as follows:
function add(a: string, b: string) : string; function add(a: number, b:number) : number; function add(a: any, b: any): any { return a + b; } console.log(`add(1,1)= ${add(1,1)}`); console.log(`add("1","1")= ${add("1","1")}`);
Here, we specify a function overload signature for the add function that accepts two strings and returns a string. We then specify a second function overload that uses the same function name but uses numbers as parameters. These two function overloads are then followed by the actual body of the function. The last two lines of this snippet are calling the add function, firstly with two numbers, and then with two strings. The output of this code is as follows:
add(1,1)= 2 add("1","1")= 11
There are three points of interest in the previous code snippet. Firstly, none of the function signatures on the first two lines of the snippet actually have a function body. Secondly, the final function definition uses the type specifier of any and eventually includes the function body. To overload functions in this way, we must follow this convention, and the final function signature (that including the body of the function) must use the any type specifier, as anything else will generate compile-time errors. The last point to note is that, even though the final function body uses the type of any, this signature is essentially hidden by using this convention. We are actually limiting the add function to only accepting either two strings, or two numbers. If we called the function with two boolean values, as follows:
console.log(add(true,false)= ${add(true,false)});
TypeScript would generate compile errors:
error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'number'.
 

Advanced types

TypeScript also has some advanced language features that can be used when working with basic types and objects. In this section of the chapter, we will take a quick look at these advanced type features, including:
  • Union types
  • Type guards
  • Type aliases
  • Null and undefined
  • Object rest and spread

Union types

TypeScript allows us to express a type as the combination of two or more other types. This technique is known as union types, and uses the pipe symbol (|). Consider the following TypeScript code:
var unionType : string | number; unionType = 1; console.log(unionType : ${unionType}); unionType = "test"; console.log(unionType : ${unionType});
Here, we have defined a variable named unionType, which uses the union type syntax to denote that it can hold either a string or a number. We are then assigning a number to this variable, and logging its value to the console. We then assign a string to this variable, and again log its value to the console. This code snippet will output the following: unionType : 1 unionType : test

Type guards

When working with union types, the compiler will still apply its strong typing rules to ensure that we are not mixing and matching our types. As an example of this, consider the following code:
function addWithUnion( arg1 : string | number, arg2 : string | number ) { return arg1 + arg2; }
Here, we are defining a function named addWithUnion, which accepts two parameters, and returns their sum. The arg1 and arg2 arguments are union types, and can therefore be either a string or a number. Compiling this code, however, will generate the following error:
error TS2365: Operator '+' cannot be applied to types 'string | number' and 'string | number'.
What the compiler is telling us here is that within the body of the function it cannot tell what type arg1 is. Is it a string, or is it a number? This is where type guards come in. A type guard is an expression that performs a check on our type, and then guarantees that type within its scope. Consider the following code:
function addWithTypeGuard( arg1 : string | number, arg2 : string | number ) : string | number { if( typeof arg1 ==="string") { // arg1 is treated as string within this code console.log('first argument is a string'); return arg1 + arg2; } if (typeof arg1 === "number" && typeof arg2 === "number") { // arg1 and arg2 are treated as numbers within this code console.log('both arguments are numbers'); return arg1 + arg2; } console.log('default return'); return arg1.toString() + arg2.toString(); }
Here, we have a function named addWithTypeGuard that takes two arguments, and is using our union type syntax to indicate that arg1 and arg2 can be either a string or a number. Within the body of the code, we have two if statements. The first if statement checks to see if the type of arg1 is a string. If it is a string, then the type of arg1 is treated as a string within the body of the if statement. The second if statement checks to see if both arg1 and arg2 are of type number. Within the body of this second if statement, both arg1 and arg2 are treated as numbers. These two if statements are our type guards. Note that our final return statement is calling the toString function on arg1 and arg2. All basic JavaScript types have a toString function by default, so we are, in effect, treating both arguments as strings, and returning the result. Let's take a look at what happens when we call this function with different combinations of types: console.log(addWithTypeGuard(1,2)= ${addWithTypeGuard(1,2)}) ; Here, we are calling the function with two numbers, and receive the following output:
both arguments are numbers addWithTypeGuard(1,2)= 3
This shows that the code has satisfied our second if statement. If we call the function with two strings:
console.log(addWithTypeGuard("1","2")= ${addWithTypeGuard("1","2")})
We can see here that the first if statement is being satisfied:
first argument is a string addWithTypeGuard("1","2")= 12
Lastly, when we call the function with a number and a string:
console.log(addWithTypeGuard(1,"2") = ${addWithTypeGuard(1,"2")}) ;
In this case, both of our type guard statements return false, and so our default return code is being hit:
default return addWithTypeGuard(1,"2")= 12 Type guards, therefore, allow you to check the type of a variable within your code, and then guarantee that the variable is of the type you expect within your block of code.

Type aliases

Sometimes, when using union types, it can be difficult to remember what types are allowed. To cater for this, TypeScript introduces the concept of a type alias, where we can create a special named type for a type union. A type alias is, therefore, a convenient naming convention for union types. Type aliases can be used wherever normal types are used, and are denoted by the type keyword. We can, therefore, simplify our code as follows:
type StringOrNumber = string | number; function addWithAlias( arg1 : StringOrNumber, arg2 : StringOrNumber ) { return arg1.toString() + arg2.toString(); }
Here, we have defined a type alias, named StringOrNumber, which is a union type that can be either a string or a number. We are then using this type alias in our function signature, to allow both arg1 and arg2 to be either a string or a number. Type aliases can also be used for function signatures, as follows:
type CallbackWithString = (string) => void; function usingCallbackWithString( callback: CallbackWithString) { callback("this is a string"); }
Here, we have defined a type alias, named CallbackWithString, which is a function that takes a single string parameter and returns a void. Our usingCallbackWithString function accepts this type alias (which is a function signature) as its callback argument type. When we need to use union types often within our code, type aliases provide an easier and more intuitive way of declaring named union types.

Null and undefined

In JavaScript, if a variable has been declared, but not assigned a value, then querying its value will return undefined. JavaScript also includes the keyword null, in order to distinguish between cases where a variable is known, but has no value (null), and where it has not been defined in the current scope (undefined). Consider the following JavaScript code:
function testUndef(test) { console.log('test parameter :' + test); } testUndef(); testUndef(null);
Here, we have defined a function named testUndef, which takes a single argument named test. Within this function, we are simply logging the value to the console. We then call it in two different ways. The first call to the testUndef function does not have any arguments. This is, in effect, calling the function without knowing, or without caring, what arguments it needs. JavaScript allows this sort of function-calling syntax. In this case, the value of the test argument within the testUndef function will be undefined, and the output will be: test parameter :undefined
The second call to the testUndef function passes null as the first argument. This is basically saying that we are aware that the function needs an argument, but we choose to call it without a value. The output of this function call will be:
test parameter :null
TypeScript has included two keywords for these cases, named null and undefined. Let's re-write this function in TypeScript, as follows:
function testUndef(test : null | number) { console.log('test parameter :' + test); }
Here, we have defined the testUndef function to allow for the function to be called with either a number value, or a null value. If we try to call this function in TypeScript without any arguments, as we did in JavaScript:
testUndef();
TypeScript will generate an error:
error TS2346: Supplied parameters do not match any signature of call target.
Clearly, the TypeScript compiler is ensuring that we call the testUndef function with either a number, or null. It will not allow us to call it without any arguments. This ability to specify that a function can be called with a null value allows us to ensure that the correct use of our function is known at compile time. Similarly, we can define an object to allow undefined values, as follows:
let x : number | undefined; x = 1; x = undefined; x = null
Here, we have defined a variable named x, which is allowed to hold either a number or undefined. We then attempt to assign the values 1, undefined, and null to this variable. Compiling this code will result in the following TypeScript error:
error TS2322: Type 'null' is not assignable to type 'number | undefined'.
The TypeScript compiler, therefore, is protecting our code, to make sure that the variable x can only hold either a number or undefined, but does not allow it to hold a null value.

Object rest and spread

When working with basic JavaScript objects, we often need to copy the properties of one object to another, or do some mixing and matching of properties from various objects. In order to facilitate these requirements, TypeScript has adopted the ES7 proposal and language syntax, which is called object rest and spread. Consider the following TypeScript code:
let firstObj = { id: 1, name : "firstObj"}; let secondObj = { ...firstObj }; console.log(secondObj.id : ${secondObj.id}); console.log(secondObj.name : ${secondObj.name});
Here, we start by defining a simple JavaScript object named firstObj, that has an id and a name property. We then use the new ES7 syntax to copy all of the properties of firstObj into another object called secondObj, by specifying { ...firstObj }. To test that this has indeed copied all properties, we then log the values of secondObj.id and secondObj.name to the console. The output of this code is as follows: secondObj.id : 1 secondObj.name : firstObj Here, we can see that the values of the id and name properties have been copied from firstObj into secondObj, using the rest and spread ES7 syntax. We can also use this syntax to combine multiple objects together, as follows: let nameObj = { name : "nameObj"}; let idObj = { id : 2}; let obj3 = { ...nameObj, ...idObj }; console.log(obj3.id : ${obj3.id}); console.log(obj3.name : ${obj3.name});
Here, we have an object named nameObj, which defines a single property named name. We then define a second object named idObj, which also defines a single property named id. Note then how we create obj3, with the syntax { ...nameObj, ...idObj }. This syntax means that we intend to copy all properties from nameObj, and all properties from idObj, into a new object named obj3. The result of this code is as follows:
obj3.id : 2 obj3.name : nameObj
This shows us that both object's properties have been merged into a single object. The rest and spread syntax to copy properties will apply these properties incrementally. In other words, if two objects both have a property with the same name, then the object property that was specified last will take precedence.

Summary

In this chapter, we took a look at TypeScript's basic types, variables, and function techniques. We saw how TypeScript introduces “syntactic sugar” on top of normal JavaScript code, to ensure strongly typed variables and function signatures. We also saw how TypeScript uses duck typing and explicit casting, and finished up with a discussion on TypeScript functions, function signatures, and overloading. In the next chapter, we will build on this knowledge and see how TypeScript extends these strongly typed rules into object-oriented concepts such as interfaces, classes, and inheritance.
 
#Typescript#tutorial#javascript
T

Tentang Tim Editorial

Profil penulis belum tersedia. Konten dikurasi oleh tim editorial kami.

Komentar

Bagian komentar akan diaktifkan segera

Artikel Terkait

Kembali ke Blog