Back to Compendiums

JS 2 TS

Discovering TypeScript through JavaScript

by matsjfunke

JavaScript was invented as a scripting language for browsers to add interactivity and dynamic behavior to web pages, since HTML and CSS only provide static structure and styling.

While JavaScript is traditionally an interpreted language, modern browser JavaScript engines compile JavaScript code to machine code for efficient execution. JavaScript is designed to interact within the browser environment, specifically with the Document Object Model (DOM).

html
1<html>
2  ├── <head>
3  │     ├── <title>
4  │     └── <meta>
5  └── <body>
6        ├── <header>
7        │     └── <h1>
8        ├── <nav>
9        │     └── <ul>
10        │         ├── <li>
11        │         └── <li>
12        └── <main>
13              ├── <section>
14              │     └── <p>
15              └── <footer>
16                    └── <p>
17

DOM Structure & Manipulation:

  • Structure: The DOM is a tree-like structure representing the HTML of a webpage. Each element, attribute, and text is a node in this tree.
  • Manipulation: JavaScript can access and modify the DOM to change the content, structure, and style of a webpage dynamically, such as adding or removing elements, changing text, or updating CSS styles.

Web APIs

Web APIs are built-in interfaces provided by browsers to interact with and manipulate various aspects of the web environment. They enable dynamic UI updates, network requests, graphics and audio processing, real-time communication, data storage, and device feature access (geolocation, battery, file handling, etc.).

JavaScript Frameworks

Frameworks are pre-written libraries that provide a structured way to build and organize web applications. They offer tools, components, and guidelines to streamline development, manage complex tasks, and ensure consistency across projects.

Popular frameworks like React, Angular, and Vue offer different approaches to building JavaScript applications, each with their own philosophies and strengths.

Beyond the Browser

Even though JavaScript was originally designed for the browser (frontend), it has also gained significant popularity on the backend, proving Jeff Atwood's law:

Any application that can be written in JavaScript, will eventually be written in JavaScript.

~ Jeff Atwood, 2009

Runtime Environments

Runtime environments like Node.js have expanded JavaScript's reach beyond the browser, enabling it to power server-side applications, desktop applications, and even mobile apps by allowing JavaScript code to execute outside of the browser environment.

JavaScript Syntax

Variables

javascript
1let a = 5; // mutable
2const b = 6; // immutable
3

Functions

javascript
1// function definition
2function add(a, b) {
3    return a + b;
4}
5
6// function expression
7const add = function(a, b) {
8    return a + b;
9}
10
11// arrow function prefered for single line functions or anonymous functions
12const add = (a, b) => a + b;
13

Functions are also objects in JavaScript meaning they can:

  • Be assigned to variables
  • Be passed as arguments
  • Have properties and methods
  • Be returned from other functions

Objects

Objects are collections of key-value pairs. this keyword that references the current object.

javascript
1// object literal syntax
2const person = {
3  name: "Brendan Eich",
4  age: 34,
5  greet: () => {
6    return `Hello, ${this.name}!`;
7  },
8};
9
10person.greet(); // "Hello, Brendan Eich!"
11person.age; // 34
12

When variables are passed into functions, they are copied, objects are referenced.

Meaning: when you pass an object into a function, you can modify the object and it will be reflected in the original object.

javascript
1const num = 1;
2const obj = new Object();
3
4doSomething(num, obj); // num is copied, obj is referenced
5

Object Oriented Programming

class is just a wrapper around the object literal syntax.

  • constructor is a method that is called when a new object is created.
  • set and get are used to set and get the value of a property.
  • static methods are called on the class itself rather than on an instance of the class.
javascript
1class Person {
2  constructor(name, age) {
3    this.name = name;
4    this.age = age;
5  }
6  set country(country) {
7    this.country = country;
8  }
9  get country() {
10    return this.country;
11  }
12  static create(name, age) {
13    return new Person(name, age);
14  }
15  greet() {
16    return `Hello, ${this.name}!`;
17  }
18  static create(name, age) {
19    return new Person(name, age);
20  }
21}
22
23const person = new Person("Brendan Eich", 34);
24person.greet(); // "Hello, Brendan Eich!"
25person.country = "USA";
26person.country; // "USA"
27

Arrays

Arrays are collections of values.

javascript
1const arr = [1, 2, 3];
2arr[0]; // 1
3arr.push(4); // [1, 2, 3, 4]
4arr.splice(0, 1); // Removes 1 element at index 0
5// arr is now [2, 3, 4]
6arr.length; // 3
7
8const mapped = arr.map((x) => [x, x + 10]);
9// mapped: [[1, 11], [2, 12], [3, 13]]
10
11const flatMapped = arr.flatMap((x) => [x, x + 10]);
12// flatMapped: [1, 11, 2, 12, 3, 13]
13

Non-blocking event loop aka. Asynchronous Processing

Usually scripts are executed line by line, but with the event loop we can achieve non-blocking operations. This is needed since websides have multiple processes running at the same time but only access the the Main Thread of the browser.

  • Single Thread: javascript uses one main worker thread to handle many tasks simultaneously.
  • Non-blocking I/O: It initiates operations (like file reads or network requests) and continues with other work without waiting for completion.
  • Callbacks & Events: When an operation finishes, javascript processes the result through callbacks or events.
  • Event Loop: A continuous system monitors and manages all pending operations and their completion.
  • Concurrency: This architecture enables handling thousands of concurrent connections without creating additional threads.

The loop is basically a queue of tasks to be executed.

javascript
1// This callback function will wait 1 second before calling the console.log function.
2setTimeout(() => {
3  console.log("1 second passed");
4}, 1000);
5

The better way to handle this is to use a Promise.

AnPromise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

javascript
1const promise = new Promise((resolve, reject) => {
2  // Something async here (like api call)
3  if (success) {
4    resolve("Success");
5  } else {
6    reject("Failure");
7  }
8});
9
10// .then() is used to handle the success case
11// .catch() is used to handle the failure case
12promise
13  .then((result) => {
14    console.log(result);
15  })
16  .catch((error) => {
17    console.log(error);
18  });
19

Async / Await

  • async functions always return a promise.
  • await is used to wait for a promise to resolve.

always wrap in a try/catch block.

javascript
1async function someAsyncFunction() {
2  try {
3    const result = await somePromise;
4    console.log(result);
5  } catch (error) {
6    console.log(error);
7  }
8}
9
10someAsyncFunction();
11

Sharing Code

Modules are used to share code between files.

ES Module syntax: import and export are used to share code between modules.

javascript
1// someFile.js
2export const someFunction = () => {
3  console.log("someFunction");
4};
5
6export const someVariable = "someVariable";
7
8export default otherFunction = () => {
9  console.log("otherFunction");
10};
11
javascript
1// main.js
2import { someFunction, someVariable } from "./someFile";
3import otherFunction from "./someFile";
4
5// no curly braces, because it is a default export
6
7someFunction();
8console.log(someVariable);
9

Modules are also installed via package manager like npm or pnpm etc. then they are in the node_modules directory and defined in the package.json file.

Note: there is also a CommonJS syntax for modules (like require and module.exports) but it is getting deprecated.

Control Structure

Logical Operators

&& the logical AND operator is used to check if both conditions are true.
|| the logical OR operator is used to check if at least one condition is true.
! the logical NOT operator is used to check if a condition is false.
!! the logical NOT operator is used to convert a value to a boolean

=== the strict equality operator is used to check if a condition is equal to another condition
>= greater than or equal to another condition
<= less than or equal to another condition
> greater than another condition
< less than another condition
!= not equal to another condition
!== not equal to another condition

?? the nullish coalescing operator is used to check if a condition is null or undefined

javascript
1if (condition && condition2) {
2  // code
3} else if (condition || condition2) {
4  // code
5} else if (!condition3) {
6  // code
7} else {
8  // code
9}
10

ternary operator is a more concise way to write an if/else statement.

javascript
1condition ? true : false;
2

switch is a more concise way to write multiple if/else statements.

javascript
1switch (expression) {
2  case value is true:
3    // code
4  case value2 === value3:
5    // code
6  default:
7    // code
8}
9

Loops

for loop is used to loop a certain number of times.

break is used to break out of a loop.

continue is used to skip the current iteration of a loop.

javascript
1for (let i = 0; i < 10; i++) {
2  // i-- would decrement i
3  // code
4  if (i >= 5) {
5    break; // break out of the loop
6  } else if (i <= 3) {
7    continue; // skip the current iteration
8  }
9}
10

while loop is used to loop until a condition is met.

javascript
1while (condition) {
2  // code
3  if (otherCondition) {
4    break; // break out of the loop
5  }
6}
7

TypeScript

Since JavaScript is a dynamically typed language where variables can hold any type of value, TypeScript enhances it by introducing static typing, allowing developers to catch type-related errors at compile time rather than at runtime.

TypeScript allows you to statically / explicitly define the type of a variable like:

typescript
1const greeting: string = "hello world";
2

Although you don't have to define the type (as TypeScript will infer the type based on the value assigned to the variable), you should always define the type of a variable, neglecting this defeats the purpose of TypeScript, same goes for any types there are little reasons to not define the type of a variable.

Spending time on typing will always pay dividends as the TextEditor will help you with type errors and autocomplete.

typescript
1// generally bad idea:
2const greeting: any = "hello world";
3

Basic / Primitive Types

typescript
1// string
2const greeting: string = "hello world"; // Represents text values.
3
4// bigint
5const bigInt: bigint = 123n; // For arbitrarily large integers.
6
7// number
8const age: number = 42; // Represents both integers and floating-point numbers.
9
10// boolean
11const isLoggedIn: boolean = true; // Represents true/false values.
12
13// undefined
14const undefined: undefined = undefined; // Value automatically assigned to uninitialized variables.
15
16// null
17const null: null = null; // Represents intentional absence of any object value.
18

Literal types are types that can only hold a specific value.

typescript
1const hello: "hello" = "hello";
2const number5: 5 = 5;
3

Union types must be one of the types in the union.

typescript
1const phoneNumber: string | number = "+491234567890";
2

Object Types

typescript
1// object
2// Any non-primitive type (arrays, functions, objects).
3const obj: object = { name: "John", age: 30 };
4
5// Array
6const arr: number[] = [1, 2, 3];
7const arr: string[] = ["1", "2", "3"];
8
9// Tuple
10// Fixed-length, ordered array types.
11const tuple: [number, string] = [1, "2"];
12// Array of tuples
13const tuples: [number, string][] = [
14  [1, "apple"],
15  [2, "banana"],
16  [3, "cherry"]
17];
18
19// Function
20const func: (x: number) => string = (x) => x.toString();
21

Generic Types

Generic types are a way to create types that can be used with different types.

typescript
1interface Box<T> {
2  value: T;
3}
4
5const stringBox: Box<string> = { value: "banana" };
6const numberBox: Box<number> = { value: 123 };
7
8const stringArray: Array<string> = ["1", "2", "3"];
9

Special Types

typescript
1// any
2// disables any type checking
3const any: any = "hello";
4
5// unknown
6// safer alternative to any, used for variables that can hold any type
7const unknown: unknown = "hello";
8
9// void
10// use for functions that don't return a value
11const void: void = undefined;
12
13// promise
14// used for asynchronous operations explained earlier
15const promise: Promise<string> = new Promise((resolve, reject) => {
16  resolve("hello");
17});
18

Enums

Enums are a way to define a set of named constants.

typescript
1enum Direction {
2  Up = "up",
3  Down = "down",
4  Left = "left",
5  Right = "right",
6}
7
8const moveUp: Direction = Direction.Up;
9console.log(moveUp); // "up"
10

Type Aliases

Type aliases are a way to give a name to a type.

typescript
1type Point = {
2  x: number;
3  y: number;
4};
5
6const point: Point = { x: 1, y: 2 };
7

Interfaces

Interfaces describe the shape of objects, mainly for object-oriented design.

typescript
1interface Person {
2  name: string;
3  age: number;
4}
5
6interface Employee extends Person {
7  id: number;
8} // use extends for inheritance
9const employee: Employee = { name: "John", age: 30, id: 1 };
10
11interface Person {
12  gender: string;
13} // Adds property to Person
14const person: Person = { name: "John", age: 30, gender: "male" };
15

Utility Types

Utility Types are built-in types that can be used to transform other types.

K is the type of the keys, T is the type of the values, meaning you can create a new type with properties of type K and values of type T.

  • Partial<T> -> Makes all properties optional
typescript
1const personName: Partial<Person> = { name: "John" };
2
  • Required<T> -> Makes all properties required
typescript
1const completePerson: Required<Person> = {
2  name: "John",
3  age: 30,
4  gender: "male",
5};
6
  • Pick<T, K> -> Picks specific properties from a type
typescript
1const personName: Pick<Person, "name"> = { name: "John" };
2
  • Omit<T, K> -> Omits specific properties from a type (opposite of Pick)
typescript
1const personWithoutAge: Omit<Person, "age"> = { name: "John", gender: "male" };
2

Pick is for objects (selecting properties). Extract is for unions (filtering types).

  • Extract<T, U> -> Extracts specific properties from a type
typescript
1type ABC = "a" | "b" | "c";
2type OnlyA = Extract<ABC, "a">; // "a"
3
  • Exclude<T, U> -> Excludes specific properties from a type (opposite of Extract)
typescript
1type NotA = Exclude<ABC, "a">; // "b" | "c"
2
  • Record<K, T> -> Creates a new type with properties of type K and values of type T
typescript
1type FruitCounts = Record<"apple" | "banana" | "cherry", number>;
2
3const fruits: FruitCounts = {
4  apple: 10,
5  banana: 5,
6  cherry: 12,
7  // orange: 3, // Error: 'orange' is not assignable to type 'FruitCounts'
8};
9

Type definitions

Generally I like the define types in a separate types.ts file, in the directory where the types originate from, this allows me the reuse and export the types and have all the logic revoling around the topic in one place.

typescript
1// main.ts
2import { Person } from "./types";
3
4// types.ts
5export type Person = {
6  name: string;
7  age: number;
8};
9
10const person: Person = { name: "John", age: 30 };
11

Note: This is compendium is opinionated, some JS and TS charactoeristics like Maps, Sets, Intersection Types... are not covered as I don't find myself using them.