JavaScript and TypeScript
JavaScript is a high-level programming language used in many places, but particularly in web development as "the language of the Web." TypeScript builds on top of JavaScript by providing a type system, ensuring that certain kinds of errors never make it to runtime.
The overwhelming majority of projects developed and maintained by ICSSC Projects use TypeScript as their primary language. This is to ensure that anyone who is interested in contributing to one project may, with minimal effort, also contribute to another project.
Below, we'll provide a quick overview of the basics of JavaScript, including some patterns that we use frequently in our codebases. We'll then introduce TypeScript's type system and how it's enforced.
In this and future parts of the Getting Started guide, we will introduce new terms in italics.
Any points that are particularly important, which we really want you to take away from this guide, will be in bold.
Supplementary notes, like this one, will be enclosed in a box like this called a Callout.
Introduction to JavaScript
Printing
As a product of the Web, the primary way that JavaScript interacts with standard input and output is through an object called console
, which as its name suggests is an interface to your browser's developer console. console
is used even in server-side code, except that it writes to the terminal instead.
Variables
Unlike some other languages, you can declare constants in JavaScript using the keyword const
, and JavaScript will make sure that it cannot be modified. It's recommended that you use const
as much as possible, and only use the keyword let
for things that absolutely must change at some point (for instance, loop counters).
In some older code you may also see the keyword var
. The use of var
is not considered best practice anymore, so you should avoid using it. let
does the same thing in 99% of cases.
Strings and string interpolation
Strings function much the same in JavaScript as in any other programming language, but one feature that JavaScript has that some others may not is string interpolation. This allows you to put any expression or value into a string, while enhancing readability because the value or expression is located where it will appear in the string, rather than after.
Equality
JavaScript has two types of equality operators, ==
and ===
(and their negations, !=
and !==
). Due to historical reasons, ==
and !=
exhibit distinctively non-intuitive behavior. For example:
For this reason, you should almost always use ===
and !==
.
Functions: arrows, anonymity, oh my!
While we're on the subject of JavaScript having many ways to do almost (but not quite) the same thing, there are many ways to declare a function, each with their own quirks. We won't be going into too much detail here, and will only make our recommendations on which ones to use (and which are commonly used in our projects' codebases).
You can declare a function the classic way, using the function
keyword:
But you can also declare an anonymous function and assign it to a variable:
Needless to say, this pattern is verbose for no real good reason, and should almost never be used.
A pattern that looks like this, but is more concise and thus very commonly used, is the arrow function:
If your arrow function returns the value of a single expression, it can be made even more concise by eliminating the curly braces, like so:
Functions are an extremely powerful part of JavaScript: they can be passed as arguments to other functions, which can enable you to write extremely clean and easily maintainable code. You'll see more examples of code written in this fashion, known as functional programming, below.
Arrays and comparators
One major quirk (and one that causes beginners hours of pain) of JavaScript arrays is that by default, its .sort()
method sorts the array in alphabetical order as if the elements were strings, even if they are not. This quirk is easily illustrated like so:
We can override this default (and frankly nonsensical) behavior by providing our own comparator function. This function just takes in two elements, and depending on their values in relation to each other, returns another value.
compareFunction(a, b) return value | Resulting sort order |
---|---|
a positive number | sort b before a |
a negative number | sort a before b |
zero | if a was first, keep it that way |
In our instance, we can have our array be sorted properly by using a simple arrow function that just subtracts b
from a
, so the ordering is correct:
Array methods
JavaScript arrays also come with a multitude of methods that can be used to do things with the array's contents. The most commonly used method is .map()
:
That's cool and all, but let's look at a problem that's a bit more complicated. Suppose you have an array of numbers, and you want to write a function that returns the sum of all the even numbers in the array. You could probably accomplish this task with loops, like so:
But what if I told you this could be a one-line arrow function?
Don't just take my word for it - paste this into your browser console or Node.js window, and see if it's true. This is the power of JavaScript's array methods, and more broadly, functional programming. For this reason, array method chaining is used quite a bit throughout our codebases.
However, it's important to recall that functional programming is not a cure-all: there are some places where loops may be more readable and make more sense than chaining array methods, and that's fine!
Objects
Objects in JavaScript are commonly used in a similar way to dictionaries or hash maps in other languages. Unlike some other languages, however, you don't need quotes around the keys. Also, you can access the values using either bracket or dot notation. This makes objects in JavaScript function similar to data classes or data transfer objects in other languages.
If you have a constant you'd like to turn into a key of an object, you can do that too by surrounding it with square brackets, like so:
Introduction to TypeScript
Now that you're more familiar with JavaScript, let's bring on some additional complexity with TypeScript.
Built-in types
TypeScript understands that JavaScript has a few built-in types, even if it doesn't have type checking. Its built-in type system is thus built upon these types:
Let's look at a few examples:
boolean
andstring
are as you would expect in any other language.number
represents any number, integer or floating-point - JavaScript doesn't make a distinction, so neither does TypeScript.undefined
andnull
are special types that represent a value which doesn't exist.undefined
is the default value of any variable that hasn't been initialized whilenull
must be assigned deliberately. However, a variable of typeundefined
cannot have the valuenull
, and vice versa.void
is the return type of a function that doesn't return anything.never
is the return type of a function that never returns, i.e. always throws an error or terminates the program.unknown
is used to denote data whose type and structure are not known.any
is similar tounknown
, but overrides type checking entirely for that object. As it defeats the purpose of TypeScript, it should be used sparingly.
Assigning types
Generally, TypeScript is smart enough to figure out the types of new variables based on what value they have. Therefore, unless the inferred type is wrong, you don't really need to declare the type explicitly.
However, if you use a let
declared variable with no initial value, TypeScript will likely require you to explicitly declare the type.
Function parameters should almost always have types, since that's how you can provide information to whoever is using your function on what types the arguments of your function takes.
Object types
Aside from built-in types, TypeScript also supports typing the values of objects. This is immensely powerful; with it, you can model data in a type-safe manner, ensuring that you don't accidentally access a property that doesn't exist, or write data of an incompatible type to a field.
There are two main ways to declare object types, type aliases and interfaces:
While they look similar, type aliases and interfaces have some subtle syntactic differences. Each ICSSC project may have its own convention on when to use type aliases or interfaces (and when not to), so it's important to follow the project's conventions.
Unions
Unions allow us to create a type that can be any one of a list of types which we provide. The most straightforward application of this is in creating something akin to enums in other languages:
You may or may not have noticed that enum
s do exist in TypeScript! However, like with var
, their use is not recommended.
Another pattern that is commonly seen in some of our projects is the tagged union. The tagged union is commonly implemented as a union of several objects that are otherwise different, but which share one property. It is extremely powerful because it's extremely easy to discern which object you're dealing with just by checking the shared property.