🍃 Leaf

JavaScript

JavaScript is a programming language; in case you are a complicated person you can also call it by its full name: high-level-single-threaded-garbage-collected-interpreted-just-in-time-compiled-prototype-based-multi-paradigm-dynamic-programming-language.

It was introduced by Brendan Eich as Mocha, renamed to LiveScript, and released in 1997 as JavaScript. Today, the European Computer Manufacturers Association’s (ECMA International) Technical Committee number 39 (TC39) maintains the ECMAScript Language Specification (ECMA-262) standard, which defines the ECMAScript Language (ES or JavaScript).

After the launch of the sixth version so called ES6 (or ES2015), which introduced major features, it’s been released one “small” update per year instead of a big one after several years.

Relevant standards:

More at:

Syntax

Good Practices

Beauty is more important in computing than anywhere else in technology because software is so complicated. Beauty is the ultimate defense against complexity. (David Gelernter, Machine Beauty: Elegance and the Heart of Technology)

Not mandatory but recommended:

Keywords

Reserved words with special functionality, we cannot define variables with these names, some of them are: await, break, case, class, const, continue, do, else, export, for, function, if, import, let, new, return, super, switch, this, var, while.

Comments

// Comment.
/* Comment. */

/* 
Comment.
Comment.
Comment.
*/
/**
 * Comment.
 * Comment.
 * Comment.
 */

Variables

// E.g.:
const a = 0; // Block-scoped, not redefinable.
let b = 1;   // Block-scoped, redefinable.
var c = 2;   // Function/Global-scoped, redefinable, redeclarable.

Data Types

In JavaScript, variables don’t have type but their values do.

Primitives & Objects

Primitive types:

Object type:

Primitives vs Objects:

PrimitivesObjects
DefinitionThey are atomic blocks of data.They are compound pieces of data.
AssignmentTheir contents are copied.Their identity (think pointers/reference) is passed.
ComparisonThey are compared by value.They are compared by identity.

Nullish

undefined // Value not defined.
null      // No value.

Booleans

true
false

Numbers

1000  // They handle values up/down to +/-9007199254740991.
1_000 // We can use underscores (_) as separators to improve legibility.
1e3   // == 1 * (10 ** 3) == 1000 (Scientific notation).
1n    // For large values, memory is the limit (Bigint notation).
// Special (not) numbers
NaN // "Not a Number".
Infinity
-Infinity

Strings

'abc123'
"abc123"
`abc12${1 + 2}` // Template Literal, it evaluates expressions (e.g.: 'abc123').

Arrays (Literal Notation)

An indexed list of items.

// E.g.:
[
  undefined,
  true,
  1,
  'abc',
  [/* ... */],
  {/* ... */}
]

Objects (Literal Notation)

A key-value collection of items.

// E.g.:
{
  nullish: undefined,
  boolean: true,
  number: 1,
  string: 'abc',
  array: [/* ... */],
  object: {/* ... */}
}
Reading Properties

We can access props through either the period-name notation or the array-like notation.

// E.g.:
const object = {
  prop: 'abc'
};

object.prop == object['prop'] // == (abc == abc) == true
Non-string Property Names (Symbol)

Symbols are meant to provide a way to generate a property name that’s not a string (which was the only way until ES6).

// E.g.:
const object = {};

const propName0 = 'prop name';
const propName1 = Symbol('symbol prop name');
const propName2 = Symbol('symbol prop name'); // Same description for both.

object[propName0] = 0;
object[propName1] = 1;
object[propName2] = 2;

console.log(object);
// {
//   prop name: 0,
//   Symbol(symbol prop name): 1,
//   Symbol(symbol prop name): 2
// }

As every Symbol generates a unique value, a property name defined from it can only be accessed through it.

// E.g.:
object[Symbol('symbol prop name')]; // == undefined
object['symbol prop name'];         // == undefined

Operators

Level of precedence for common operators:

  1. &&, ||, ?? (parentheses are needed if mixing ?? with && or ||).
  2. <, >, <=, >= ==, ===.
  3. **.
  4. /, *, %.
  5. +, -.

Logical

true && true  // == 2
false && true // == false (`true` is never evaluated).
true || true  // == true (second `true` is never evaluated).
false || true // == true

Nullish Coalescing

a ?? b // == `b` iff `a` == (`null` || `undefined`).

Comparison

// Quantitative:
1 < 2  // == true
2 > 1  // == false
1 <= 1 // == true
1 >= 1 // == false
// Qualitative:
1 == '1'      // == true (Equal, type is coerced if needed/posible).
1 === '1'     // == false (Strictly Equal, no "type coercion" here).
true != 0     // == true
true !== true // == false

Maths

1 ** 2 // == 1 (Power operator).
1 / 2  // == 0.5
1 * 2  // == 2
1 % 2  // == 1 (Remainder operator).
1 + 2  // == 3
1 - 2  // == -1

Ternary Operator (Expression)

a ? b : c // If `a` is truthy: `b`. Else: `c`.

String Concatenation

'abc' + 'def' // == abcdef (better use the string `concat` method).

Unary Operators

typeof x // Returns the operand's type name as a string.
x++      // Evaluates `x`, then adds 1 to it.
++x      // Adds 1 to `x`, then evaluates the updated `x`.
x--
--x
+"+1.00" // == 1 (Converts a string into its numeric value/equivalent).
!true    // == false (Negates a boolean expression).
// Example of `x++` & `++x`:
let a = 1, b = 1;

a + b++;     // == 2 == 1 + 1 (`b` doesn't change until after its evaluation).
++a + b;     // == 4 == 2 + 2 (Adds 1 to `a` before evaluating it).
a++ + b + a; // == 7 == 2 + 2 + 3 (The 2nd evaluation of `a` counts the 1st +1).
a+++--b;     // == 4 == 3 + 1 (Typing it all together is "legal").
a+++++b      // Error, +3 consecutive, equal operators (use parentheses).

Compound Assignment Operators

We can assign and operate with the same (compound) operator, so instead of this shape:

variable = variable (operator) value

We can use this short hand:

variable (operator)= value

Some of the available operators:

variable += value
variable -= value
variable *= value
variable /= value
variable %= value
variable **= value
variable ||= value
variable &&= value
variable ??= value
// E.g.:
let variable = 0;

variable += 1; // == (variable = variable + 1) == 1

The instanceof Operator

An operator that verifies whether an object is an instance of a given constructor or not.

// E.g.:
class Car { /* ... */ }

const array = [];
const car = new Car();

console.log(array instanceof Object); // --> true
console.log(array instanceof Car); // --> false
console.log(car instanceof Car); // --> true

Control Flow

Statement if

// Executes iff `condition` evaluates truthy.
if (condition) {
  // ...
}

// Chain an alternative to `condition` with an `else` statement.
if (condition) {
  // ...
} else {
  // I.e. executes iff `condition` evaluates falsy.
}

// Or chain more several conditions on the same statement with `else if`.
if (condition0) {
  // ...
} else if (condition1) {
  // ...
} else if (conditionN) {
  // ...
}

Statement switch

// E.g.:
switch (input) {
  case (condition0):
    // Runs if `condition0` is truthy (continues evaluation).
  case (condition1):
    // Runs if `condition1` is truthy.
    break; // Stops evaluation (works also in `for` and `while` statements).
  case (conditionN):
    // ...
  default:
    // Runs by default (in case no `break` stopped evaluation before).
}

Statement try / catch / finally

If we know something can break the code: we can just try it; if something goes wrong we catch it, and no matter what happens; we can finally run some extra code at the end.

// E.g.:
try {
  // ...
} catch (error) {
  // Runs only if `try` produces an error.
} finally {
  // ...
}

Loops

Loop for

// Shape:
for (/* Initialization */;/* Condition */;/* Step */) {
  // ...
}

Shape description:

// E.g.:
for (let i = 0; i < 3; i++) {
  console.log(i);
}
// --> 0
// --> 1
// --> 2
/**
 * We can still run a `for` loop without an initialization, condition, or step
 * (or even with none of them). A loop equivalent to the first one would be:
 */
let i = 0;

for (;;) {
  console.log(i++);
  if (i >= 3) break;
}
// --> 0
// --> 1
// --> 2

Loop for-of

/**
 * Loops through each item of a given iterable. A brand new `item` is declared
 * on each iteration, that's why it can be a `const` (same for "For-in").
 */
for (const item of iterable) {
  // ...
}
// E.g.:
const colors = [
  'red',
  'blue',
  'green'
];

for (const color of colors) {
  console.log(color);
}
// --> red
// --> blue
// --> green

Loop for-in

// Loops through each property in a given object.
for (const property in object) {
  // ...
}
// E.g.:
const shape = {
  name: 'triangle',
  sides: 3,
  vertex: 3
};

for (const prop in shape) {
  console.log(`${prop}: ${shape[prop]}`);
}
// --> name: triangle
// --> sides: 3
// --> vertex: 3

// Note that `prop` is actually the name of the property, not its value.

Loop while

// Loop starts if `condition` evaluates truthy, continues until it gets falsy.
while (condition) {
  // ...
}

Loop do-while

// Loop starts, then continues if/until `condition` evaluates falsy.
do {
  // ...
} while (condition)

Reusable Code Blocks, Functions

A function Statement

// E.g.:
function square(x) {
  return x * x;
}

A function Expression

// E.g.:
const square = function (x) {
  return x * x;
}

Arrow (=>) Function (Expression)

// E.g.:
const square = (x) => {
  return x * x;
};
// Simplified notation:
const square = x => x * x;

Simplification explained:

  1. If there’s only a return expression: it can be skipped (it’s implied).
  2. If there’s only one statement: braces can be skipped.
  3. If there’s only one parameter: parentheses can be skipped.

IIFE (Immediately Invoked Function Expression)

A function that’s executed as soon as it’s reached. It’s made up of two pairs of parentheses:

// E.g.:
(function () {
  console.log('This is an IIFE!');
})();
// --> This is an IIFE!

Closure

It’s just an ordinary function that holds declarations connected to its original scope (another function wrapping it).

// E.g.:
function multiplier(a) {
  return function (b) {
    return a * b;
  }
}

const half = multiplier(.5);
console.log(half(2)) // --> 1

Generator Function (*) & yield Statement

A function identified by an asterisk after the keyword function that doesn’t execute when called, but instead returns an iterable object (each item from it will be served by a yield statement). Then we can use an iterator or manually iterate through its items with the next method, which holds a value and checks whether it’s done or not.

// E.g.:
function* charactersByIndex(string) {
  const characters = string.split('');

  for (let i = 0; i < characters.length; i++) {
    yield `Character at index ${i} is ${characters[i]}`;
  }
}
// Iterating with `next`:
const ABC = charactersByIndex('ABC');

console.log(ABC.next().value); // --> Character at index 0 is A
console.log(ABC.next().value); // --> Character at index 1 is B
console.log(ABC.next().value); // --> Character at index 2 is C

console.log(ABC.next().value); // --> undefined
console.log(ABC.next().done);  // --> true
// Iterating with an iterator (recommended):
const DEF = charactersByIndex('DEF');

for (const item of DEF) {
  console.log(item);
}
// --> Character at index 0 is D
// --> Character at index 1 is E
// --> Character at index 2 is F

Classes

Classes are meant to build objects through a more sophisticated process, letting us produce more elaborated structures and functionality.

A class Declaration

Even though it’s not mandatory, we often use capitalized names.

class Animal {
  // ...
}
// Class expression:
const Animal = class Animal {
  // ...
}

The new Keyword

It’s used to instantiate (create) objects from classes.

// E.g.:
class Animal {
  // ...
}

const animal = new Animal();
console.log(animal);
// --> Animal {}

Fields: Properties & Methods

Object fields are:

Use the this keyword to refer to the corresponding object inside the class.

// E.g.:
class Fox {
  said = 'ring-ding-ding';
  talk() {
    return `The fox says ${this.said}!`;
  }
}

let fox = new Fox();
console.log(fox.talk());
// --> The fox says ring-ding-ding!

The constructor Method

It’s used to parameterize object creation.

// E.g.:
class Dog {
  constructor (name, noise) {
    this.name = name;
    this.noise = noise;
  }
}

const dog = new Dog('Scooby', 'woof');
console.log(dog);
// --> Dog { name: 'Scooby', noise: 'woof' }

Subclasses: extends & super Keywords

Description:

// E.g.:
class Vehicle {
  constructor(type, capacity) {
    this.type = type;
    this.capacity = capacity;
  }
}

class Car extends Vehicle {
  constructor(brand) {
    // Note that specific properties can only be defined after the `super` call.
    super('land', 4);
    this.brand = brand;
    this.wheels = 4;
  }
}

class Tesla extends Car {
  constructor() {
    super('Tesla');
  }
  plug() {
    console.log('Charging battery...');
  }
}

Public & Private (#) Fields

Fields are public by default, but we can make them private by adding a hash as a prefix.

// E.g.:
class Coke {
  #formula = '🧪 + 🍬 + 🍋';  
}

const coke = new Coke();
console.log(coke.#formula); // Error (it's not accessible from outside).
// Legal use of private fields (from inside the class):
class FlavoredCoke {
  #formula = '🧪 + 🍬 + 🍋';
  constructor(flavor) {
    this.#formula = `${this.#formula} + ${flavor}`;
  }
  tellSecretFormula() {
    console.log(this.#formula);
  }
}

const cherryCoke = new FlavoredCoke('🍒');
cherryCoke.tellSecretFormula();
// --> 🧪 + 🍬 + 🍋 + 🍒

Instance & static Fields

Fields are “instance” by default, that means they can be used from an instantiated object. The static keyword defines fields that can only be called from the class itself.

// E.g.:
class Vector2D {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  static add(v1, v2) { // To be used as `Vector2D.add(v1, v2)`.
    return new Vector2D(
      v1.x + v2.x,
      v1.y + v2.y
    );
  }
  add(v) { // To be used as `vector.add(v)`.
    this.x += v.x;
    this.y += v.y;
  }
}

const vector1 = new Vector2D(3, 3);
const vector2 = new Vector2D(3, 3);
const vector3 = Vector2D.add(vector1, vector2);
console.log(vector3);
// --> Vector2D { x: 6, y: 6 }

vector2.add(vector3);
console.log(vector2);
// --> Vector2D { x: 9, y: 9 }

The get & set Methods

Methods that behave like properties, we don’t call them but define, read their values.

// E.g.:
class Square {
  constructor(side) {
    this.side = side;    
  }
  get area() {
    return this.side ** 2;
  }
  set area(area) {
    this.side = Math.sqrt(area);
  }
}

const square = new Square(10);
console.log(square.area); // `get`
// --> 100

square.area = 25; // `set`
console.log(square.side);
// --> 5

Timers & Animations

Delay a Call (setTimeout)

Use setTimeout to delay a function call by a given amount of time (in milliseconds). It won’t delay or pause any other code as it runs asynchronously. Use clearTimeout to abort it while waiting.

// E.g.:
const timeout1 = setTimeout(() => console.log(1), 1800);
const timeout2 = setTimeout(() => console.log(2), 1200);
const timeout3 = setTimeout(() => console.log(3), 600);

clearTimeout(timeout2);
// --> 3 (after 600ms)
// --> 1 (after 1800ms)

Wait, Call, Repeat (setInterval)

Use setInterval to indefinitely repeat a function call after a given amount of times. Stop an interval with clearInterval. The first call occurs after the given time.

// E.g.:
const interval = setInterval(() => console.log('Hi!'), 500);
setTimeout(() => clearInterval(interval), 2000);
// --> Hi! (after 0.5s)
// --> Hi! (after 1.0s)
// --> Hi! (after 1.5s)
// --> Hi! (after 2.0s, some environments could run the timeout first).

A Call per Frame (requestAnimationFrame)

Use requestAnimationFrame to make a function call just before the next repaint. Inside that call we can read and write the DOM many times without triggering synchronous reflows.

requestAnimationFrame(() => { /* ... */ });

Call requestAnimationFrame again inside the same function to make an actual animation (using recursion). Ideally it’ll run every ~16ms, and extend the execution for less than that time when targeting 60 frames per seconds for animating the UI. Use cancelAnimationFrame to stop it.

// E.g.:
let raf;

function animation() {
  // Perform some tasks...
  
  raf = requestAnimationFrame(animation);
}

// Initialize animation:
requestAnimationFrame(animation);

// Stoping the animation e.g.:
window.addEventListener('keypress', event => {
  event.key === 'Enter' && cancelAnimationFrame(raf);
});

Asynchronous Programming

By default (most of) the code runs synchronously, which means it runs one step at a time, even if a step takes too long to finish (delaying next steps). Asynchronous programming solves that by letting code run in parallel (kind of).

Using a Promise

It’s an analogy to the real world where we establish a promise which will either resolve or reject. In it we pass a “successful” result through resolve (which fires the first then method), or a “failing” result through reject (which fires the catch method).

// E.g.:
const promise = new Promise((resolve, reject) => {
  resolve('Asynchronous log.');
});

promise
  .then(result => console.log(result))
  .catch(error => console.log(error));
console.log('Synchronous log.');
// --> Synchronous log.
// --> Asynchronous log.

// Note that any promise lets synchronous code run first to then run itself.
Either resolve or reject

From inside the Promise we should use any conditional statement or mechanism to execute either resolve or reject but not both at the same time, the first call will trigger its own path, either then or catch (but we can still use final at the end).

// E.g.:
const promise = new Promise((resolve, reject) => {
  reject('Reject.');   // This runs (catch).
  resolve('Resolve.'); // This will be ignored.
});

promise
  .then(result => console.log(result))
  .catch(error => console.log(error));
// Reject.
// However, if something "fails" at `then`, `catch` will execute, e.g.:
const promise = new Promise((resolve) => {
  resolve('Success.');
});

promise
  .then(result => {
    console.log(result);
    throw 'Error thrown from `then`.';
  })
  .catch(error => console.log(error));
// --> Success.
// --> Error thrown from `then`.
Static Methods of Promise

A Promise is not only used to generate a promise but to handle utility methods too.

// E.g.:
const promise0 = new Promise((resolve, reject) => reject('Error (0).'));
const promise1 = new Promise((resolve, reject) => resolve('Success (1).'));
const promise2 = new Promise((resolve, reject) => resolve('Success (2).'));
// Method `all`: runs until either all promises resolve (array) or one rejects.
Promise.all([promise0, promise1, promise2])
  .then(results => console.log(results))
  .catch(error => console.log(error));
// --> Error (0).
/**
 * Method `any`: runs until getting a `resolve` or throws an error (different
 * from catch) if none of them resolves ("All the promises were rejected").
 */
Promise.any([promise0, promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.log(error));
// --> Success (1).
// Method `race`: runs until getting either a `resolve` or a `reject`.
Promise.race([promise0, promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.log(error));
// --> Error (0).

Asynchronous Functions (async & await)

A simpler approach than using a Promise. They actually return a Promise which resolves with the returned value. We can use await expressions inside these functions (or at global scope in modules) to suspend the execution until we get the resolved (returned) value, making it possible to work with a much easier, synchronous-like notation.

// E.g. (module):
async function foo() {
  return 'bar'; // Suppose it returns with a long-running task.
}

console.log(await foo()); // --> bar

We can use then, catch, and finally on them like any other Promise.

// E.g.:
async function foo() {
  return 'bar'; // Suppose it returns with a long-running task.
}

foo()
  .then(resolve => console.log(resolve))
  .catch(error => console.log(error));
// --> bar

Regular Expression Library (RegExp)

Creating a RegExp

Special syntax characters (can be escaped by a prefix backslash): \, ^, $, ., *, +, ?, (, ), {, }, |.

// Literal notation, e.g.:
let re = /abc/g;
// Constructor with a string, e.g.:
let re = new RegExp('abc', 'g');
// Constructor with a literal, e.g.:
let re = new RegExp(/abc/, 'g');

Option Flags

FlagPropertyDescription
dhasIndicesSwitch on math indices
gglobalMatch multiple times
iignoreCaseMatch case-insensitively
mmultiline^ and $ match per line
sdotAllDot matches line terminators
uunicodeUnicode mode (recommended)
ystickyNo characters between matches

Basic Building Blocks for RegExp

Atoms and some Backslash codes:

More at Character Classes (developer.mozilla.org). *More about character property values at (unicode.org) Property Value Aliases, Unicode Regular Expressions, and Unicode Identifier and Pattern Syntax.

Assertions

Anchors

Ways to define what’s at the beginning or end:

Lookahead

Define what should be after the match:

Lookbehind

Define what should be before the match:

Disjunction (|)

A low precedence operator that makes the match to be either what’s at the left or what’s at the right side of it (groups/parentheses recommended), e.g.:

Character Classes ([...])

A way to create a set characters, e.g.:

Or a range of characters ([n-m], including boundaries), e.g.:

Characters that need to be escaped inside a class in order to make part of the set:

Quantifying Expressions

By default these quantifiers are greedy, they match as many characters as possible. To make them reluctant, so they match as few as possible, put a question mark (?) after them:

// E.g.:
console.log(/(abc)+/.exec('abcabcabc')[0]);  // --> 'abcabcabc' (greedy).
console.log(/(abc)+?/.exec('abcabcabc')[0]); // --> 'abc' (reluctant).

Capturing Groups ((...))

A way to reuse subexpressions (in the actual RegExp or a replacement expression):

Backreference to those groups:

// E.g.:
console.log(/(abc)\1(123)\2/.test('abcabc123123'));      // --> true
console.log('abc123'.replace(/(\w{3})(\d{3})/, '$2$1')); // --> 123abc

The test Method (RegExp)

It’s used to verify whether there is a match or not.

// E.g.:
console.log(/a/.test('abc')); // --> true
console.log(/a/.test('xyz')); // --> false

The exec Method (RegExp)

It returns the first match and its capturing groups (if any).

// E.g.:
console.log(.../(a|x)(bc)(123)/.exec('abc123xbc123')); // --> abc123 a bc 123

The search Method (String)

It returns the first match’s index.

// E.g.:
console.log('abcabc'.search(/c/)); // --> 2

The match Method (String)

It returns the match and its capturing groups (if any). Note that global flag (g) returns all possible matches, but prevents capturing groups from making part of the output.

// E.g.:
console.log(...'abc123xbc123'.match(/(a|x)(bc)(123)/));  // abc123 a bc 123
console.log(...'abc123xbc123'.match(/(a|x)(bc)(123)/g)); // abc123 xbc123

The replace Method (String)

It replaces a match by a given replacement expression.

console.log('abc'.replace(/(\w)/g, '$1$1$1 ')); // --> aaa bbb ccc

Date & Time Library (Date)

The Date object represents the current time since 1 January 1970 UTC.

// E.g.:
console.log(new Date());
// --> Thu Jun 30 2022 17:10:39 GMT-0300 (Uruguay Standard Time)

Creating a Date from Data

We can create a Date object for a specific date & time by passing:

// E.g. (respectively):
console.log(new Date(864255128369).toString());
console.log(new Date(97, 4, 21, 19, 52, 8, 369).toString());
console.log(new Date('1997-05-21T19:52:08.369').toString());
console.log(new Date('1997-05-21T19:52:08.369-03:00').toString());
console.log(new Date('1997-05-21T22:52:08.369Z').toString());
// --> Wed May 21 1997 19:52:08 GMT-0300 (Uruguay Standard Time)
// --> Wed May 21 1997 19:52:08 GMT-0300 (Uruguay Standard Time)
// --> Wed May 21 1997 19:52:08 GMT-0300 (Uruguay Standard Time)
// --> Wed May 21 1997 19:52:08 GMT-0300 (Uruguay Standard Time)
// --> Wed May 21 1997 19:52:08 GMT-0300 (Uruguay Standard Time)

Formatting Date & Time

Different ways to display date & time as a string:

// String:
console.log((new Date()).toString());
console.log((new Date()).toDateString());
console.log((new Date()).toTimeString());
// --> Sat Jul 02 2022 08:57:38 GMT-0300 (Uruguay Standard Time)
// --> Sat Jul 02 2022
// --> 08:57:38 GMT-0300 (Uruguay Standard Time)
// Localized string:
console.log((new Date()).toLocaleString());     // --> 7/2/2022, 8:57:38 AM
console.log((new Date()).toLocaleDateString()); // --> 7/2/2022
console.log((new Date()).toLocaleTimeString()); // --> 8:57:38 AM
// Others:
console.log((new Date()).toUTCString()); // --> Sat, 02 Jul 2022 11:57:38 GMT
console.log((new Date()).toISOString()); // --> 2022-07-02T11:57:38.533Z

Getters & Setters from Date

A list of methods for reading and writing specific (and relative) date & time units. Apart from getTime and getTimezoneOffset, all the other methods have a corresponding version of: get<unit>, getUTC<unit>, set<unit>, and setUTC<unit>.

// E.g. (for `get<unit>`):
console.log((new Date()).getTime());           // --> 1656763181562
console.log((new Date()).getTimezoneOffset()); // --> 180 (from Uruguay).
console.log((new Date()).getFullYear());       // --> 2022
console.log((new Date()).getMonth());          // --> 6 (0-indexed).
console.log((new Date()).getDate());           // --> 2 (Month day).
console.log((new Date()).getDay());            // --> 6 (Week day).
console.log((new Date()).getHours());          // --> 8
console.log((new Date()).getMinutes());        // --> 59
console.log((new Date()).getSeconds());        // --> 41
console.log((new Date()).getMilliseconds());   // --> 562

Equivalent Time Between now vs getTime

We don’t always need to create a new instance of Date to call getTime and get the current time in milliseconds. Instead we can get it by calling the now method straight from the Date object.

// E.g.:
console.log(Date.now() === (new Date).getTime()); // --> true

The JSON Library

A tool to create and parse JSON (JavaScript Object Notation) structured data, which is a way to store raw text in a similar syntax than objects from JavaScript.

Syntax, Objects vs JSON

Main differences:

// E.g.:
{
  "section": "JSON",
  "finished": true,
  "sources": [
    "JavaScript for Impatient Programmers",
    "MDN Web Docs"
  ]
}

From JSON to JavaScript (parse)

Use the parse method to convert a JSON text to a JavaScript value.

// E.g.:
console.log(JSON.parse('{ "names": ["parse", "stringify"] }'));
// --> { names: [ 'parse', 'stringify' ] }

From JavaScript to JSON (stringify)

Use the stringify method to convert a JavaScript value to a JSON text.

// E.g.:
console.log(JSON.stringify({ names: [ 'parse', 'stringify' ] }));
// --> {"names":["parse","stringify"]}

HTTP Requests

Requesting with XMLHttpRequest

XHR is a low-level API for server interaction. It can retrieve data from a URL updating part of the page, which wasn’t possible before, as every request used to trigger a complete page reload.

XHR Usage
  1. Instantiate a request object.
  2. Open a connection to an endpoint.
  3. Send a request.
  4. Listen/Await for a response.
// E.g.:
const request = new XMLHttpRequest();
request.open('GET', 'https://jsonplaceholder.typicode.com/users');
request.send();
request.onload = () => console.log(request.response); // --> [{ ... }, ...]
// request.abort(); // Not possible with fetch.
XHR Properties
// E.g.:
request.onload = () => {
  console.log(request.response);       // --> [{ ... }, ...]
  console.log(request.readyState);     // --> 4
  console.log(request.status);         // --> 200
  console.log(request.statusText);     // --> "" (empty, "OK" by default)
  // console.log(request.responseText) // *
};

/**
 * (*) We can get a response through responseText, but only if responseType is
 * left as '' (empty) or set to 'text', otherwise we would get an error.
 */
XHR Events
// E.g.:
request.abort = () => { /* ... */ };
request.error = () => { /* ... */ };
request.load = () => { /* ... */ };
request.loadend = () => { /* ... */ };
request.loadstart = () => { /* ... */ };
request.progress = () => { /* ... */ };
request.readystatechange = () => { /* ... */ };
request.timeout = () => { /* ... */ };
XHR Response Types

If open has its third argument (asynchronous) set to false, then responseType must be set before send (i.e.: before LOADING and DONE states). Notice that setting it to 'json' will automatically parse the response.

Enumerable values:

// E.g.:
request.responseType = 'json';
Setting XHR Request Headers

Headers can only be set after open but before send calls:

// E.g.:
request.open('GET', 'https://jsonplaceholder.typicode.com/users');
// ...
request.setRequestHeader('Content-Type', 'application/json');
// ...
request.send();
XHR State

The property readyState is the request’s current state, it changes at every phase of the process (from 0 to 4). XHR will use the server’s response to set both status (HTTP response status code) and it’s respective statusText (e.g.: 'OK', 'Not Found', etc).

Correspondent values:

readyStatestatusstatusTextSame as property
0Always 0Always ''UNSENT
1Always 0Always ''OPENED
2Always 0 (not sure!)Always '' (not sure!)HEADERS_RECEIVED
31xx|2xx|3xx|4xx|5xx''|'OK'|'Not found'|...LOADING
41xx|2xx|3xx|4xx|5xx''|'OK'|'Not found'|...DONE

Reacting to status change event:

// E.g.:
request.readystatechange = () => {
  switch (request.readyState) {
    case request.UNSENT:
      // ...
      break;
    case request.OPENED:
      // ...
      break;
    case request.HEADERS_RECEIVED:
      // ...
      break;
    case request.LOADING:
      // ...
      break;
    case request.DONE:
      // ...
      break;
  }
};

Requesting with fetch

Fetch is a high-level API, easier but less capable compared with XHR, although the most noticeable difference is that it does not trigger page reloads.

Multi-threading & Web Workers API

We previously defined JavaScript as a single threaded programming language. However, we can use the Workers API to cast threads.

A worker is an object that references to a different global scope from a different file, a file that must be in the same origin that main JavaScript file is running in. Workers won’t work under the file:// protocol.

Dedicated Workers

A dedicated worker is just a Worker, i.e., the common one.

We use the postMessage method and the onmessage event handler to communicate between scripts, where postMessage triggers the execution of the correspondent onmessage. Data passed through gets copied, not shared.

// main.js e.g.:
const worker = new Worker('./worker.js');

// When `worker.js` sends a message:
worker.onmessage(message => {
  // ...
});

worker.postMessage('Message sent from main.js');
// worker.js e.g.:

// When `main.js` sends a message:
self.onmessage(message => {
  // ...
});

postMessage('Message sent from worker.js');

Two aspects to notice here:

First, the assignment inside worker.js could be written as var onmessage = /* ... */. However let or const won’t work.

Second, the self, also inside worker.js is an alias for globalThis. More precisely, it’s a property of WorkerGlobalScope that returns a reference to the WorkerGlobalScope itself.

WorkerGlobalScope provides a way to load external scripts (something that couldn’t be done otherwise through import statements), the importScripts method.

// worker.js e.g.:
importScripts('foo.js', 'bar.js');

A Worker instance has two methods:

And several event methods from which we can highlight:

Code Modularization

A module allows us to isolate code between files. To share items (variables, functions, and classes) we need to use the export and import statements. We can share things “named” as they are, or locally override their name as we want. It’s also possible to export an item by default, without needing to use a specific name (only one per file).

Named export & import

Exporting:

const pi = 3.14;
const phi = 1.6;

// Multiple items at once, e.g.:
export { pi, phi };
// One item at a time, e.g.:
export const pi = 3.14;
export const phi = 1.6;

Importing:

// E.g.:
import { pi, phi } from 'util.js';

console.log(pi); // --> 3.14
console.log(phi); // --> 1.6

Sharing by default

Exporting:

// E.g.:
const pi = 3.14; // Constants cannot be default exported directly.
export default pi;
export default function () { /* ... */ }
// As we import items with any name, the exported local name is not used.
export default function unusedFunctionName() { /* ... */ }

Importing:

// No braces needed for `default` exported items. Also the name can vary.
import util from 'util.js';

Sharing with Aliases (as)

Exporting:

// E.g.: one.js
const one = 1;
export { one as ONE };

Importing:

// E.g.: main.js
import { ONE as NUMBER_ONE } from 'one.js';
console.log(NUMBER_ONE); // --> 1

Glossary

Statement vs Expression

Statement

It’s a piece of code that performs an action.

// E.g.: a function statement.
function square(x) {
  return x ** 2;
}

// Or an if statement.
if (x > 0) {
  console.log('X is greater than zero.');
}

Expression

It’s a piece of code that produces a value.

// E.g.: the value assigned to a variable (`false`).
let a = false;

// Or the value returned by a `function` (`x * .5`).
function half(x) {
  return x * .5;
}

// Or an IIFE (Immediately Invoked Function Expression).
(function (x) {
  console.log(x);
})(1);
// --> 1

// Or a logic expression (`2 > 1`).
console.log(2 > 1);
// --> true

Declaration vs Definition

Declaration

It’s the creation of an identifier.

// E.g.: declaring a `function`.
function saySomething() {
  console.log('Something.');
}

// Or declaring variables.
const a = true;
let b = 1;
var c = 'abc';
/**
 * Both `const` and `let` redeclarations are not allowed, e.g.:
 * 
 * let b;
 * --> Identifier 'b' has already been declared (Error).
 *
 * However, `var` redeclarations are legal, e.g.:
 */
var c = 'abc';
var c; // No errors here.

/**
 * Note that redeclaring without redefining doesn't assign `undefined` to the
 * variable, which can happen at the first time (see "Definition" below).
 */
console.log(c);
// --> abc
Shadowing Variables

As said before, we cannot declare the same name for const and let at the same level of scope because it would be an illegal attempt of redeclaration. However, what we can do is to declare the same name but in a nested scope.

// E.g.:
const x = 1;

{
  const x = 2;
  console.log(x); // --> 2
}

console.log(x); // --> 1

Definition

It’s the binding of value to an identifier.

// E.g.: we can declare a variable without necessarily defining it.
let x; // It implicitly initializes as `undefined` i.e.: `let x = undefined;`.

console.log(x);
// --> undefined
// For obvious reasons, `const` needs to be defined at its declaration, e.g.:
const y = 0;
// Definitions with `var` are the only ones that work before declaration, e.g.:
y = 2;
var y; // It's like `var y;` were first (see "Hoisting").

Parameter vs Argument

Parameter

It’s an identifier passed into a function declaration.

// E.g.: set `a` and `b` as parameters of `sum`.
function sum(a, b) {
  return a + b;
}

Argument

It’s an expression passed through a function call (i.e. the parameter’s value).

// E.g.: pass `2` and `-2` as arguments for `sum`.
sum(2, -2);
// --> 0

Visibility

Hoisting

It’s a behavior from the interpreter engine of sending declarations to the top of their scope. Only var and function declarations are hoisted.

// E.g.:
sayHi();
// --> Hi!

function sayHi() {
  console.log('Hi!');
}

// Function definitions (assigned to a variable) are not hoisted.
const sayBye = function () {
  console.log('Bye!');
}

sayBye(); // It needs to be called after its definition to avoid an error.
// --> Bye!
// Note hoisting moves only declarations, not definitions, e.g.:
console.log(x);
// --> undefined

var x = 3; // I.e. the `var x` is hoisted, not the `= 3`.
console.log(x);
// --> 3
// However, there should be a `var` declaration, if not, code breaks, e.g.:
console.log(y);
// --> `y` is not defined (Error).

y = 6; // This is not a declaration, thus it's not hoisted.
// Note that definition also triggers declaration if not already declared, e.g.:
z = 9;
console.log(z);
// --> 9

Scope

The range of visibility for a given block of code and its variables.

// This is the "global" scope.
let a = 1;

{
  // This is a nested scope block.
  let b = 2;
  var c = 3;

  // Scopes share declarations with nested scopes, that's called Lexical Scope.
  console.log(a); // --> 1
}

console.log(c); // --> 3 (`var` limits its scope to global or function blocks).
console.log(b); // --> `b` is not defined (Error, we cannot see `b` from here).

Flow of control

It’s the order of execution. JavaScript reads from top-to-bottom, like a natural language.

// E.g.: because of hoisting, a function call can occur before its declaration.
foo();
function foo() { /* ... */ }

// But function definitions are under the top-to-bottom flow of control.
bar(); // `bar` is not defined (Error).
const bar = function () { /* ... */ }

Reference: