DevTools

Cheatsheet JavaScript

Linguagem de programação para web e aplicações

Back to languages
JavaScript
93 cards found
Categories:
Versions:

Basic


13 cards
Variable Declaration
let name = "Anna";        // mutable, block scope
const PI = 3.14159;      // constant, not reassignable
var old = "avoid";   // function scope (legacy)

// let can be reassigned
name = "Mary";

// const cannot be reassigned
// PI = 3;  // TypeError!

// const with objects: the reference is fixed
const user = { name: "John" };
user.name = "Peter";  // OK - mutates the content
// user = {};         // TypeError!

Use const by default and let when you need to reassign. Avoid var — it has function scope and causes bugs with hoisting. const doesn't prevent object mutation, only reassignment of the reference.

Truthy and Falsy
// FALSY values (6 in total):
false, 0, "", null, undefined, NaN

// Everything else is TRUTHY:
true, 1, -1, "text", [], {}, "0", "false"

// Practical examples
if ("") { }       // doesn't run (falsy)
if ("text") { }  // runs (truthy)
if ([]) { }       // runs! (empty array is truthy)
if ({}) { }       // runs! (empty object is truthy)

// Check real boolean
Boolean("");     // false
Boolean("oi");   // true
!!0;             // false (double negation)
!!"text";       // true

// Common use: default value
let name = input || "Anonymous";

JavaScript has 6 falsy values: false, 0, "", null, undefined and NaN. Everything else is truthy, including empty arrays and objects. Use !! to convert to a real boolean.

Destructuring
// Arrays
const [a, b, c] = [1, 2, 3];
const [x, , z] = [1, 2, 3];  // skips index 1
const [first, ...rest] = [1, 2, 3, 4];

// Default values
const [name = "Anonymous"] = [];

// Objects
const { name, age } = { name: "Anna", age: 30 };
const { name: n, age: a2 } = { name: "Anna", age: 30 };
// rename: n = "Anna", a2 = 30

// Nested
const { addr: { city } } = { addr: { city: "Porto" } };

// In function parameters
function show({ name, age }) {
  console.log(`${name}, ${age}`);
}
show({ name: "Anna", age: 30 });

// Swap variables
let x = 1, y = 2;
[x, y] = [y, x];  // x=2, y=1

Destructuring extracts values from arrays or object properties into variables. It supports defaults, renaming ({ name: n }), nesting and rest. Widely used in function parameters.

typeof and instanceof
// typeof: returns a string with the type
typeof "text";     // "string"
typeof 42;          // "number"
typeof true;        // "boolean"
typeof undefined;   // "undefined"
typeof {};          // "object"
typeof [];          // "object" (doesn't distinguish!)
typeof null;        // "object" (bug!)
typeof function(){};// "function"
typeof Symbol();    // "symbol"

// instanceof: checks prototype
[] instanceof Array;   // true
[] instanceof Object;  // true
new Date() instanceof Date;  // true

// Check array correctly
Array.isArray([1,2]);  // true
Array.isArray({});     // false

// Check null
const isNull = val === null;
const isNil = val == null;  // null OR undefined

typeof returns the type the a string but fails with arrays and null. Use Array.isArray() for arrays and === null for null. instanceof checks the prototype chain.

Primitive Types
// 7 primitive types
let str = "text";          // string
let num = 42;               // number
let big = 9007199254740991n; // bigint
let bool = true;            // boolean
let undef = undefined;      // undefined
let null = null;            // null (object)
let sym = Symbol("id");     // symbol

// Check type
typeof str;    // "string"
typeof num;    // "number"
typeof bool;   // "boolean"
typeof undef;  // "undefined"
typeof null;   // "object" (historical bug!)
typeof sym;    // "symbol"
typeof big;    // "bigint"

JavaScript has 7 primitive types. typeof null returns "object" — it's a historical bug in the language. Use === null to check for null explicitly.

Type Conversion
// String → Number
Number("42");       // 42
parseInt("42px");   // 42
parseFloat("3.14"); // 3.14
+"42";              // 42 (unary)

// Number → String
String(42);         // "42"
(42).toString();    // "42"
`${42}`;            // "42"

// → Boolean
Boolean(1);         // true
Boolean("");        // false
!!0;                // false

// Implicit coercion (avoid!)
"5" + 3;    // "53" (concatenates!)
"5" - 3;    // 2    (converts!)
true + 1;   // 2
[] + {};    // "[object Object]"

// Check NaN
isNaN(NaN);         // true
Number.isNaN(NaN);  // true (safer)

Use Number(), parseInt() or the unary + to convert strings to numbers. Watch out for implicit coercion: "5" + 3 concatenates ("53") but "5" - 3 converts (2). Prefer Number.isNaN() over isNaN().

Essential console.log
// Basic
console.log("Value:", 42);
console.warn("Warning!");
console.error("Error!");
console.info("Info");

// CSS formatting
console.log("%c Text blue", "color: blue");

// Table (arrays/objects)
console.table([{ a: 1, b: 2 }, { a: 3, b: 4 }]);

// Object with depth
console.dir(obj, { depth: null });

// Group
console.group("Group");
console.log("inside");
console.groupEnd();

// Assert
console.assert(1 === 2, "1 not is 2!");

Besides console.log(), use console.table() for arrays/objects, console.dir() to inspect structure and console.group() to organize related logs.

Comparison Operators
// == vs === (ALWAYS use ===)
5 == "5";      // true  (type coercion!)
5 === "5";     // false (no coercion)
null == undefined;  // true
null === undefined; // false

// Relational operators
10 > 5;       // true
10 <= 10;     // true
"a" < "b";    // true (alphabetical order)

// Logical operators
true && false;  // false (AND)
true || false;  // true  (OR)
!true;          // false (NOT)

// Nullish coalescing (??)
let val = null ?? "default";  // "default"
let zero = 0 ?? "default";    // 0 (not null!)

Always use === and !== instead of == and != to avoid implicit coercion. The ?? (nullish) operator only triggers on null or undefined, unlike || which also triggers on 0 and "".

Optional Chaining (?.)
const user = {
  name: "Anna",
  address: {
    street: "Av. Central",
    city: "Lisbon"
  }
};

// Without optional chaining (verbose)
const city = user && user.address && user.address.city;

// With optional chaining
const city2 = user?.address?.city;  // "Lisbon"
const country = user?.address?.country;       // undefined (no error!)

// With methods
const upper = user?.getName?.();  // undefined if it doesn't exist

// With arrays
const arr = [1, 2, 3];
arr?.[0];    // 1
arr?.[10];   // undefined

// Combine with nullish
const name = user?.name ?? "Anonymous";

The ?. (optional chaining) accesses properties without error if the object is null/undefined. It returns undefined instead of throwing TypeError. Combine with ?? for default values.

console — Performance and Counting
// Measure time
console.time("operation");
// ... code ...
console.timeEnd("operation");  // "operation: 2.5ms"

// Counter
console.count("click");  // "click: 1"
console.count("click");  // "click: 2"
console.countReset("click");

// Trace (call stack)
function a() { b(); }
function b() { console.trace(); }
a();  // shows the call stack

// debugger: pauses in DevTools
function calculate(x) {
  debugger;  // manual breakpoint
  return x * 2;
}

console.time()/timeEnd() measures performance. console.count() counts calls. console.trace() shows the call stack. debugger; creates a breakpoint in the code.

Template Literals
const name = "Anna";
const age = 30;

// Interpolation with backticks
const msg = `Hello, ${name}! You are ${age} years old.`;

// Expressions inside
const total = `Total: ${2 + 3 * 4}€`;  // "Total: 14€"

// Multi-line
const html = `
  <div>
    <h1>${name}</h1>
    <p>Age: ${age}</p>
  </div>
`;

// Function inside
const upper = `Name: ${name.toUpperCase()}`;

// Ternary inside
const status = `${age >= 18 ? "Adult" : "Minor"}`;

Template literals use backticks ` and allow interpolation with ${expression}. They support multi-line without \n and accept any JavaScript expression inside ${}.

Spread and Rest (...)
// SPREAD: expand elements
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];  // [1,2,3,4,5]

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };  // {a:1, b:2, c:3}

// Copy (shallow copy)
const copy = [...arr1];
const objCopy = { ...obj1 };

// REST: collect arguments
function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);  // 10

// Destructuring with rest
const [first, ...rest] = [1, 2, 3, 4];
// first = 1, rest = [2,3,4]

const { a, ...rest } = { a: 1, b: 2, c: 3 };
// a = 1, rest = {b:2, c:3}

The ... operator has two uses: spread expands arrays/objects and rest collects remaining arguments. It creates shallow copies — nested objects share the reference.

Ternary Operator
// Syntax: condition ? valueIfTrue : valueIfFalse
const age = 20;
const status = age >= 18 ? "Adult" : "Minor";

// Chained (avoid more than 2 levels)
const grade = 15;
const result = grade >= 18 ? "Excellent"
  : grade >= 14 ? "Good"
  : grade >= 10 ? "Sufficient"
  : "Insufficient";

// In JSX/templates
// {isAdmin ? <AdminPanel /> : <UserPanel />}

// With nullish for defaults
const name = user?.name ?? "Anonymous";

// Conditional assignment
let msg;
if (isAdmin) msg = "Welcome admin";
else msg = "Welcome";
// Equivalent:
const msg2 = isAdmin ? "Welcome admin" : "Welcome";

The ternary operator condition ? a : b is a compact conditional expression. Ideal for simple assignments. Avoid chaining more than 2 levels — use if/else for complex logic.

Strings


6 cards
Search and Checking
const str = "Hello World JavaScript";

str.length;              // 20
str.indexOf("World");    // 4
str.lastIndexOf("a");    // 17
str.includes("World");   // true
str.startsWith("Hello");   // true
str.endsWith("Script");  // true
str.search(/world/i);    // 4 (regex)

// match (returns matches)
"2024-01-15".match(/\d+/g);  // ["2024","01","15"]

// at() - negative index
"JavaScript".at(-1);   // "t"
"JavaScript".at(-6);   // "S"

indexOf() returns the position (or -1). includes(), startsWith() and endsWith() return a boolean. at(-1) accesses the last character without computing the index.

Unicode and Characters
// Unicode code
"A".charCodeAt(0);         // 65
String.fromCharCode(65);   // "A"

// Code points (emoji)
"😀".codePointAt(0);       // 128512
String.fromCodePoint(128512);  // "😀"

// Iterate with for...of (respects Unicode)
for (const char of "Hello 😀") {
  console.log(char);
}

// Normalization
"café".normalize("NFC");

// Escape
"line1\nline2";   // new line
"tab\taqui";        // tab
"aspas \"here\"";   // quotes

// String.raw (no escape)
String.raw`C:\new\folder`;  // "C:\new\folder"

// Locale comparison
"a".localeCompare("b");  // -1
"ã".localeCompare("a");  // 1

Use for...of to iterate strings with emoji (respects surrogate pairs). String.raw ignores escape sequences. localeCompare() compares strings with accent support.

String Transformation
const str = "Hello World JavaScript";

str.toUpperCase();       // "HELLO WORLD JAVASCRIPT"
str.toLowerCase();       // "hello world javascript"
str.slice(4, 9);         // "World"
str.substring(4, 9);     // "World"
str.replace("World", "JS");  // "Hello JS JavaScript"
str.replaceAll("a", "o");    // "Hello World JovaScript"
str.trim();              // removes whitespace at both ends
str.repeat(2);           // repeats the string
str.charAt(0);           // "O"

// concat
"Hello".concat(" ", "World");  // "Hello World"

Strings are immutable — methods return new strings. slice() accepts negative indices, replace() only replaces the first occurrence (use replaceAll() for all).

localeCompare and Normalization
// Locale comparison
"a".localeCompare("b");      // -1
"ã".localeCompare("a", "pt"); // 1 (after)
"Water".localeCompare("banana", "pt"); // -1

// Sort with accents
["action", "Water", "banana"].sort(
  (a, b) => a.localeCompare(b, "pt")
);
// ["Water", "action", "banana"]

// Unicode normalization
"café".normalize("NFC");   // composed form
"café".normalize("NFD");   // decomposed form

// Check visual equality
const s1 = "\u00E9";       // is (composed)
const s2 = "e\u0301";      // is (decomposed)
s1 === s2;                  // false!
s1.normalize() === s2.normalize();  // true

localeCompare() compares strings respecting language and accents. normalize() standardizes Unicode representations — essential for comparing accented strings from different sources.

split, padStart and search
// split: string → array
"a,b,c".split(",");       // ["a","b","c"]
"hello world".split(" ");   // ["hello","world"]
"abc".split("");          // ["a","b","c"]

// join: array → string
["a","b","c"].join("-");  // "a-b-c"

// padStart / padEnd
"5".padStart(3, "0");     // "005"
"42".padEnd(5, ".");      // "42..."

// search (with regex)
"Hello World".search(/world/i);  // 4

// match (returns matches)
"2024-01-15".match(/\d+/g);  // ["2024","01","15"]

// at() - negative index
"JavaScript".at(-1);   // "t"
"JavaScript".at(-6);   // "S"

// concat
"Hello".concat(" ", "World");  // "Hello World"

split() divides a string into an array by delimiter. padStart()/padEnd() pad up to a given length. at(-1) accesses the last character without computing the index.

Basic Regex
// Creation
const re1 = /default/gi;
const re2 = new RegExp("default", "gi");

// Flags: g (global), i (case-insensitive), m (multiline)

// Test
/^\d+$/.test("12345");    // true
/^\d+$/.test("12a45");    // false

// Extract
const email = "ana@mail.com";
email.match(/(\w+)@(\w+)/);
// [0]="ana@mail", [1]="ana", [2]="mail"

// Replace
"2024-01-15".replace(/-/g, "/");  // "2024/01/15"

// Common metacharacters
// \d = digit, \w = word char, \s = whitespace
// . = any, ^ = start, $ = end
// + = 1+, * = 0+, ? = 0 or 1
// {n,m} = between n and m

// Example: validate a simple email
const emailRe = /^[\w.]+@[\w]+\.[\w]{2,}$/;
emailRe.test("ana@mail.com");  // true

Regex in JS uses /pattern/flags. .test() checks for a match, .match() extracts groups and .replace() substitutes. Metacharacters: \d (digit), \w (word char), ^/$ (start/end).

Numbers and Math


5 cards
Rounding and Powers
// Rounding
Math.round(4.5);    // 5
Math.floor(4.9);    // 4 (rounds down)
Math.ceil(4.1);     // 5 (rounds up)
Math.trunc(4.9);    // 4 (cuts the decimal)

// Minimum and maximum
Math.max(1, 5, 3);  // 5
Math.min(1, 5, 3);  // 1
Math.max(...[1,5,3]);  // 5 (spread)

// Power and root
Math.pow(2, 10);    // 1024
2 ** 10;            // 1024 (operator)
Math.sqrt(16);      // 4
Math.cbrt(27);      // 3

// Absolute value
Math.abs(-5);       // 5

Math.floor() rounds down, Math.ceil() rounds up, Math.round() to the nearest. Use ** instead of Math.pow() for powers. Math.max(...arr) with spread for arrays.

Number Systems
// Binary (0b)
0b1010;        // 10
0b11111111;    // 255

// Octal (0o)
0o17;          // 15
0o777;         // 511

// Hexadecimal (0x)
0xFF;          // 255
0x1A;          // 26

// Visual separator (ES2021)
1_000_000;     // 1000000
0xFF_FF;       // 65535

// Convert bases
(255).toString(16);   // "ff"
(255).toString(2);    // "11111111"
(10).toString(8);     // "12"

// Parse from other bases
parseInt("ff", 16);   // 255
parseInt("1010", 2);  // 10

Prefixes: 0b binary, 0o octal, 0x hexadecimal. toString(base) converts to a string in another base. parseInt(str, base) parses from another base. Underscores (1_000) improve readability.

Random and Constants
// Random
Math.random();              // 0 to 0.999...
Math.floor(Math.random() * 10);  // 0 to 9

// Integer between min and max (inclusive)
function aleatorio(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
aleatorio(1, 6);  // 1 to 6 (dice)

// Constants
Math.PI;    // 3.14159...
Math.E;     // 2.71828...
Math.SQRT2; // 1.41421...

// Round with decimals
function roundDec(num, casas) {
  const f = 10 ** casas;
  return Math.round(num * f) / f;
}
roundDec(3.14159, 2);  // 3.14

Math.random() returns a float between 0 and 1. Math.floor(Math.random() * n) generates integers from 0 to n-1. Math.PI and Math.E are constants. For decimal places, multiply, round and divide.

Number Formatting
const num = 1234.5678;

// Decimal places
num.toFixed(2);       // "1234.57" (string!)
num.toPrecision(4);   // "1235"

// Locale (currency, separators)
num.toLocaleString("pt-PT");
// "1 234,568"

// Currency
num.toLocaleString("pt-PT", {
  style: "currency",
  currency: "EUR"
});  // "1 234,57 €"

// Percentage
(0.75).toLocaleString("pt-PT", {
  style: "percent"
});  // "75%"

// Scientific notation
(1234567).toExponential(2);  // "1.23e+6"

// parseInt / parseFloat
parseInt("42px");     // 42
parseFloat("3.14em"); // 3.14
parseInt("0xFF", 16); // 255 (hex)

toFixed() returns a string — use Number() or + to convert back. toLocaleString() formats with the locale's separators and currency. parseInt() accepts a base the the 2nd argument.

Number: Static Methods
// Checking
Number.isInteger(42);      // true
Number.isInteger(4.5);     // false
Number.isFinite(Infinity); // false
Number.isNaN(NaN);         // true
Number.isNaN("42");        // false (no coercion!)

// Limits
Number.MAX_SAFE_INTEGER;   // 9007199254740991
Number.MIN_SAFE_INTEGER;   // -9007199254740991
Number.MAX_VALUE;          // 1.7976931348623157e+308
Number.EPSILON;            // 2.220446049250313e-16

// Float comparison
0.1 + 0.2 === 0.3;  // false!
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;  // true

// BigInt (huge numbers)
const big = 9007199254740993n;
big + 1n;  // 9007199254740994n
// big + 1;  // TypeError! (mixing types)

Number.isNaN() doesn't coerce (unlike the global isNaN()). Floats have imprecision: 0.1 + 0.2 !== 0.3. Use BigInt (suffix n) for integers above MAX_SAFE_INTEGER.

Arrays


8 cards
Create and Access
// Creation
const arr = [1, 2, 3];
const arr2 = new Array(5);      // [empty x 5]
const arr3 = Array.of(1, 2, 3);
const arr4 = Array.from("abc"); // ["a","b","c"]
const arr5 = Array.from({length: 5}, (_, i) => i);
// [0, 1, 2, 3, 4]

// Access
arr[0];          // 1
arr.at(-1);      // 3 (last one!)
arr.length;      // 3

// Check
arr.includes(2);       // true
arr.indexOf(2);        // 1
arr.findIndex(x => x > 1);  // 1

// Fill
const filled = new Array(3).fill(0);  // [0,0,0]

// Sequence
[...Array(5).keys()];  // [0,1,2,3,4]

Array.from() converts iterables into arrays. at(-1) accesses the last element without arr[arr.length-1]. Array.from({length: n}, fn) creates arrays with generated values.

find, some and every
const users = [
  { name: "Anna", age: 25 },
  { name: "Peter", age: 30 },
  { name: "Mary", age: 35 },
];

// find: first that satisfies
users.find(u => u.age > 28);
// { name: "Peter", age: 30 }

// findIndex: index of the first
users.findIndex(u => u.name === "Mary");  // 2

// some: does ANY satisfy?
users.some(u => u.age > 30);   // true

// every: do ALL satisfy?
users.every(u => u.age > 20);  // true
users.every(u => u.age > 30);  // false

// includes (primitive value)
[1, 2, 3].includes(2);  // true

// findLast (ES2023)
users.findLast(u => u.age > 28);
// { name: "Mary", age: 35 }

find() returns the first element that satisfies the condition. some() checks if any passes, every() if all pass. findLast() searches from the end to the start.

Add and Remove
const arr = [1, 2, 3];

// Add
arr.push(4);       // [1,2,3,4] (end)
arr.unshift(0);    // [0,1,2,3,4] (start)

// Remove
arr.pop();         // removes last → 4
arr.shift();       // removes first → 0

// splice: remove/insert at position
arr.splice(1, 2);       // removes 2 from index 1
arr.splice(1, 0, 9, 8); // inserts 9,8 at index 1

// slice: copy part (doesn't mutate)
arr.slice(1, 3);   // [2, 3]
arr.slice(-2);     // last 2

// concat (doesn't mutate)
[1,2].concat([3,4]);  // [1,2,3,4]
[...[1,2], ...[3,4]]; // [1,2,3,4] (spread)

// flat: flatten
[1, [2, [3]]].flat();    // [1, 2, [3]]
[1, [2, [3]]].flat(2);   // [1, 2, 3]
[1, [2, [3]]].flat(Infinity);  // [1, 2, 3]

push/pop operate at the end, unshift/shift at the start. splice() mutates the original array; slice() returns a copy without mutating. flat(Infinity) flattens completely.

Sorting
// sort() MUTATES the original array!
const nums = [10, 5, 40, 25];

// Numeric ascending
[...nums].sort((a, b) => a - b);
// [5, 10, 25, 40]

// Numeric descending
[...nums].sort((a, b) => b - a);
// [40, 25, 10, 5]

// Strings (alphabetical)
["banana","apple","cherry"].sort();
// ["apple", "banana", "cherry"]

// Objects by property
const users = [{n:"Anna",i:25},{n:"Peter",i:20}];
users.sort((a, b) => a.i - b.i);

// toSorted (ES2023 - doesn't mutate!)
const sorted = nums.toSorted((a, b) => a - b);
// nums stays [10, 5, 40, 25]

// Locale compare (accents)
["action","banana","Water"].sort(
  (a, b) => a.localeCompare(b, "pt")
);

sort() mutates the original array — use [...arr].sort() or toSorted() (ES2023) to avoid mutating. Without a comparator, it sorts the strings ("40" < "5"). Use localeCompare() for accents.

map and filter
const nums = [1, 2, 3, 4, 5];

// map: transforms each element
const double = nums.map(n => n * 2);
// [2, 4, 6, 8, 10]

// filter: keeps those that pass the test
const even = nums.filter(n => n % 2 === 0);
// [2, 4]

// map with index
["a","b","c"].map((val, i) => `${i}:${val}`);
// ["0:a", "1:b", "2:c"]

// Chain
const result = nums
  .filter(n => n > 2)
  .map(n => n * 10);
// [30, 40, 50]

map() transforms each element and returns a new array. filter() selects those that pass the test. Neither mutates the original. Chain them for transformation pipelines.

forEach, for...of and entries
const arr = ["a", "b", "c"];

// forEach (returns nothing)
arr.forEach((val, i) => {
  console.log(i, val);
});

// for...of (allows break/continue)
for (const val of arr) {
  if (val === "b") break;
  console.log(val);
}

// entries (index + value)
for (const [i, val] of arr.entries()) {
  console.log(i, val);
}

// keys / values
[...arr.keys()];    // [0, 1, 2]
[...arr.values()];  // ["a", "b", "c"]

// traditional for (performance)
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

// flatMap (map + flat(1))
[[1,2],[3,4]].flatMap(a => a.map(x => x * 2));
// [2, 4, 6, 8]

forEach() doesn't allow break — use for...of when you need to stop the iteration. entries() gives index + value. flatMap() is map() + flat(1) combined.

reduce — Accumulator
const nums = [1, 2, 3, 4, 5];

// Sum
const sum = nums.reduce((acc, n) => acc + n, 0);
// 15

// Reduce to object (group)
const grouped = [6.1, 4.2, 6.3].reduce((acc, n) => {
  const key = Math.floor(n);
  acc[key] = (acc[key] || 0) + 1;
  return acc;
}, {});  // {4: 1, 6: 2}

// Full pipeline
const total = nums
  .filter(n => n > 2)
  .map(n => n * 10)
  .reduce((acc, n) => acc + n, 0);
// 120

// flatMap (map + flat(1))
[[1,2],[3,4]].flatMap(a => a.map(x => x * 2));
// [2, 4, 6, 8]

reduce() accumulates all elements into a single value. The 2nd argument is the accumulator's initial value. Use it to sum, group or transform into an object. flatMap() is map() + flat(1) combined.

ES2023 Methods (Non-Mutating)
const arr = [3, 1, 4, 1, 5];

// toSorted (doesn't mutate!)
const sorted = arr.toSorted((a, b) => a - b);
// [1, 1, 3, 4, 5] | arr unchanged

// toReversed (doesn't mutate!)
const inverted = arr.toReversed();
// [5, 1, 4, 1, 3]

// toSpliced (doesn't mutate!)
const sem2 = arr.toSpliced(1, 2);
// [3, 1, 5] | removes 2 from index 1

// with (replace by index)
const trocado = arr.with(0, 99);
// [99, 1, 4, 1, 5]

// findLast / findLastIndex
arr.findLast(n => n < 4);       // 1
arr.findLastIndex(n => n < 4);  // 3

ES2023: toSorted(), toReversed(), toSpliced() and with() return new arrays without mutating the original. findLast() searches from the end to the start.

Objects


6 cards
Create and Manipulate Objects
// Literal
const obj = { name: "Anna", age: 30 };

// Access
obj.name;           // "Anna"
obj["age"];       // 30

// Add/modify
obj.email = "ana@mail.com";
obj.age = 31;

// Remove
delete obj.email;

// Check
"name" in obj;              // true
obj.hasOwnProperty("name"); // true

// Keys, values, entries
Object.keys(obj);    // ["name", "age"]
Object.values(obj);  // ["Anna", 31]
Object.entries(obj); // [["name","Anna"],["age",31]]

// Iterate
for (const [key, val] of Object.entries(obj)) {
  console.log(key, val);
}

Objects are collections of key-value pairs. Object.keys(), Object.values() and Object.entries() return iterable arrays. delete removes a property.

JSON
const obj = { name: "Anna", age: 30, active: true };

// Object → JSON string
const json = JSON.stringify(obj);
// '{"name":"Anna","age":30,"active":true}'

// Formatted (pretty print)
JSON.stringify(obj, null, 2);

// With key filter
JSON.stringify(obj, ["name", "age"]);

// JSON string → Object
const parsed = JSON.parse(json);
parsed.name;  // "Anna"

// With reviver (transform values)
JSON.parse(json, (key, val) => {
  return key === "age" ? val + 1 : val;
});

// Careful: not supported
// undefined, functions, Symbol, Date (becomes string)
JSON.stringify(new Date());  // "2024-01-15T..."

JSON.stringify() converts an object to a JSON string. JSON.parse() does the reverse. It doesn't support undefined, functions or Symbol. Dates become ISO strings.

Object.assign and Freeze
// Merge (shallow)
const a = { x: 1 };
const b = { y: 2 };
const c = Object.assign({}, a, b);  // {x:1, y:2}
const d = { ...a, ...b };           // same (spread)

// Clone (shallow)
const clone = { ...a };
const clone2 = Object.assign({}, a);

// Freeze (immutable)
const config = Object.freeze({ port: 3000 });
config.port = 8080;  // ignored (strict: error)
config.port;         // 3000

// Check
Object.isFrozen(config);  // true

// Seal (can't add/remove, but can edit)
const sealed = Object.seal({ a: 1 });
sealed.a = 2;    // OK
sealed.b = 3;    // ignored

// Compare objects
Object.is(NaN, NaN);  // true (=== gives false!)

Object.assign() and spread do a shallow merge. Object.freeze() makes it immutable (can't add, remove or edit). Object.is() is like === but handles NaN correctly.

Computed Properties and fromEntries
// Computed property names
const field = "name";
const obj = { [field]: "Anna", [field + "2"]: "Mary" };
// { name: "Anna", nome2: "Mary" }

// Dynamic key in method
const method = "greet";
const person = {
  [method]() { return "Hello!"; }
};
person.greet();  // "Hello!"

// Object.fromEntries (inverse of entries)
const even = [["name", "Anna"], ["age", 30]];
Object.fromEntries(even);
// { name: "Anna", age: 30 }

// Transform object values
const prices = { a: 10, b: 20, c: 30 };
const withVat = Object.fromEntries(
  Object.entries(prices).map(([k, v]) => [k, v * 1.23])
);
// { a: 12.3, b: 24.6, c: 36.9 }

Computed properties ([expression]) allow dynamic keys. Object.fromEntries() is the inverse of Object.entries() — it converts pairs into an object. Useful for transforming object values.

Getters and Setters
const person = {
  _name: "Anna",
  _age: 30,

  get name() {
    return this._name.toUpperCase();
  },

  set name(val) {
    if (val.length < 2) throw new Error("Name short");
    this._name = val;
  },

  get info() {
    return `${this._name}, ${this._age} years`;
  }
};

person.name;       // "ANA" (getter)
person.name = "Mary";  // setter
person.info;       // "Mary, 30 years"

// Object.defineProperty
Object.defineProperty(person, "id", {
  value: 123,
  writable: false,
  enumerable: false
});

Getters (get) and setters (set) let you control access to properties. They're accessed like normal properties (no parentheses). Object.defineProperty() defines attributes like writable and enumerable.

Map and Set
// Map: object with keys of any type
const map = new Map();
map.set("name", "Anna");
map.set(42, "number");
map.set({id: 1}, "object");

map.get("name");    // "Anna"
map.has(42);        // true
map.size;           // 3
map.delete(42);

// Iterate Map
for (const [key, val] of map) {
  console.log(key, val);
}

// Set: collection of unique values
const set = new Set([1, 2, 2, 3, 3]);
set.size;           // 3 (duplicates removed!)
set.add(4);
set.has(2);         // true
set.delete(1);

// Remove duplicates from array
const unique = [...new Set([1,1,2,3,3])];
// [1, 2, 3]

Map accepts keys of any type (objects, numbers) and keeps insertion order. Set stores unique values. Use [...new Set(arr)] to remove duplicates from an array.

Functions


6 cards
Arrow Functions
// Traditional
function sum(a, b) {
  return a + b;
}

// Arrow function
const soma2 = (a, b) => a + b;

// One parameter (parentheses optional)
const double = n => n * 2;

// Multi-line body
const calculate = (a, b) => {
  const result = a * b;
  return result + 1;
};

// No parameters
const hello = () => "Hello!";

// Returning an object (parentheses!)
const create = (name) => ({ name, active: true });

// Difference: this
// Arrow does NOT have its own this (inherits from scope)
const obj = {
  name: "Anna",
  // WRONG with arrow:
  // speak: () => console.log(this.name),
  // RIGHT with a normal function:
  speak() { console.log(this.name); }
};

Arrow functions (=>) have compact syntax and no this of their own — they inherit it from the enclosing scope. To return an object literal, wrap it in parentheses: () => ({...}).

Function Methods
function greet(greeting, name) {
  return `${greeting}, ${name}!`;
}

// call: invokes with this and separate args
greet.call(null, "Hello", "Anna");
// "Hello, Anna!"

// apply: invokes with this and an array of args
greet.apply(null, ["Hello", "Anna"]);
// "Hello, Anna!"

// bind: creates a new function with fixed this
const olaAna = greet.bind(null, "Hello", "Anna");
olaAna();  // "Hello, Anna!"

// Practical use: fix this
const btn = document.querySelector("button");
btn.addEventListener("click",
  this.handleClick.bind(this)
);

// Partial application
const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
double(5);  // 10

call() and apply() invoke with this set (they differ in args: separate vs array). bind() creates a new function with a fixed this — useful in event handlers.

Default and Rest Parameters
// Default parameters
function greet(name = "World", punctuation = "!") {
  return `Hello, ${name}${punctuation}`;
}
greet();          // "Hello, World!"
greet("Anna");     // "Hello, Anna!"
greet("Anna", "."); // "Hello, Anna."

// Rest parameters
function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3);  // 6

// arguments (legacy - avoid)
function legacy() {
  console.log(arguments.length);
}

// Named parameters (via destructuring)
function createUser({ name, age = 18, active = true }) {
  return { name, age, active };
}
createUser({ name: "Anna", age: 25 });

Default parameters (= value) kick in when the argument is undefined. Rest (...args) collects arguments into an array. Named parameters via destructuring are ideal for functions with many options.

Recursion and IIFE
// Recursion: function that calls itself
function factorial(n) {
  if (n <= 1) return 1;  // base case
  return n * factorial(n - 1);
}
factorial(5);  // 120

// Recursion with accumulator (tail)
function sum(n, acc = 0) {
  if (n === 0) return acc;
  return sum(n - 1, acc + n);
}
sum(100);  // 5050

// IIFE: Immediately Invoked Function Expression
(function() {
  const private = "not accessible outside";
  console.log(private);
})();

// IIFE with arrow (modern)
(() => {
  const config = { debug: true };
  // initial setup...
})();

Recursion: a function that calls itself — it always needs a base case. IIFE runs immediately and creates a private scope. Useful for initial setup and avoiding global scope pollution.

Closures
// Closure: function that "remembers" the scope where it was created
function counter() {
  let count = 0;
  return {
    increment: () => ++count,
    decrement: () => --count,
    value: () => count
  };
}

const c = counter();
c.increment();  // 1
c.increment();  // 2
c.value();        // 2
// count is not directly accessible!

// Practical use: factory function
function multiplier(factor) {
  return (n) => n * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
double(5);   // 10
triple(5);  // 15

// Closure in loops (classic)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 0, 1, 2 (let creates scope per iteration)

A closure is a function that keeps access to the scope where it was created, even after that scope ends. It enables private data and factories. Use let in loops to create scope per iteration.

Callbacks and Higher-Order
// Callback: function passed the an argument
function process(arr, callback) {
  return arr.map(callback);
}
process([1,2,3], n => n * 2);  // [2,4,6]

// Higher-order: receives/returns functions
function apply(fn, value) {
  return fn(value);
}
apply(Math.sqrt, 16);  // 4

// Composition
const pipe = (...fns) => (x) =>
  fns.reduce((acc, fn) => fn(acc), x);

const transform = pipe(
  x => x * 2,
  x => x + 1,
  x => `Result: ${x}`
);
transform(5);  // "Result: 11"

// Debounce (delayed callback)
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

A callback is a function passed the an argument. Higher-order functions receive or return functions. pipe() composes functions in sequence. debounce() delays execution until calls stop.

Flow Control


4 cards
if/else and switch
// if/else
if (age >= 18) {
  console.log("Adult");
} else if (age >= 12) {
  console.log("Teenager");
} else {
  console.log("Child");
}

// switch
switch (day) {
  case 1:
  case 2:
  case 3:
  case 4:
  case 5:
    console.log("Useful");
    break;
  case 6:
  case 7:
    console.log("Weekend");
    break;
  default:
    console.log("Invalid");
}

// switch with expressions
switch (true) {
  case grade >= 18: console.log("Excellent"); break;
  case grade >= 14: console.log("Good"); break;
  default: console.log("Sufficient");
}

if/else for simple conditions. switch for multiple values — don't forget the break! switch(true) lets you compare expressions like a chained if/else.

for, for...of and for...in
// traditional for
for (let i = 0; i < 5; i++) {
  console.log(i);
}

// for...of (arrays, strings, Map, Set)
for (const item of [1, 2, 3]) {
  console.log(item);
}

// for...in (object keys)
for (const key in { a: 1, b: 2 }) {
  console.log(key);  // "a", "b"
}

// for...of with index
for (const [i, val] of ["a","b","c"].entries()) {
  console.log(i, val);
}

for...of iterates values (arrays, strings, Map, Set). for...in iterates keys (objects). Prefer for...of with .entries() to get index + value in arrays.

while, break and continue
// while
let n = 0;
while (n < 5) {
  n++;
}

// do...while (runs at least once)
do {
  n--;
} while (n > 0);

// break and continue
for (let i = 0; i < 10; i++) {
  if (i === 3) continue;  // skips 3
  if (i === 7) break;     // stops at 7
  console.log(i);  // 0,1,2,4,5,6
}

// Label (nested loops)
outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) break outer;
  }
}

while repeats while the condition is true. do...while runs at least once. break exits the loop, continue skips to the next iteration. Labels let you exit nested loops.

Advanced Logical Operators
// Short-circuit evaluation
const name = input || "Anonymous";  // OR: 1st truthy
const config = user && user.config;  // AND: 1st falsy or last

// Nullish coalescing (??)
const val = 0 ?? "default";   // 0 (not null!)
const val2 = null ?? "default";  // "default"

// Logical assignment (ES2021)
let a = null;
a ??= "default";  // a = "default"

let b = 0;
b ||= 10;  // b = 10 (0 is falsy)
b ??= 20;  // b = 10 (0 is not null)

let c = { x: 1 };
c.x &&= 2;  // c.x = 2 (x exists and is truthy)

// Chaining comparisons
// WRONG: 1 < x < 10
// RIGHT:
const inRange = x > 1 && x < 10;

|| returns the first truthy, && the first falsy or the last. ?? only triggers with null/undefined. Logical assignment operators: ??=, ||=, &&=.

Dates and Time


4 cards
Date Object
// Creating dates
const now = new Date();
const data = new Date(2024, 0, 15);  // Jan 15, 2024
const data2 = new Date("2024-01-15");
const data3 = new Date(2024, 0, 15, 14, 30, 0);

// Getters
now.getFullYear();   // 2024
now.getMonth();      // 0-11 (0 = January!)
now.getDate();       // 1-31
now.getDay();        // 0-6 (0 = Sunday)
now.getHours();      // 0-23
now.getMinutes();    // 0-59
now.getTime();       // ms since 1970

// Setters
data.setFullYear(2025);
data.setMonth(11);  // December

// Timestamp
Date.now();  // current ms

// Comparing
const d1 = new Date("2024-01-01");
const d2 = new Date("2024-12-31");
d1 < d2;  // true

getMonth() returns 0-11 (0 = January!). Date.now() gives the timestamp in ms. To compare dates, use the < and > operators directly.

Date Formatting
const data = new Date(2024, 0, 15, 14, 30);

// toLocaleDateString
data.toLocaleDateString("pt-PT");
// "15/01/2024"

data.toLocaleDateString("pt-PT", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});
// "Monday, January 15, 2024"

// toLocaleTimeString
data.toLocaleTimeString("pt-PT");
// "14:30:00"

// ISO
data.toISOString();
// "2024-01-15T14:30:00.000Z"

// Difference between dates (in days)
const d1 = new Date("2024-01-01");
const d2 = new Date("2024-01-15");
const diffDias = (d2 - d1) / (1000 * 60 * 60 * 24);
// 14

toLocaleDateString() formats with a locale. Subtracting dates gives milliseconds — divide by 1000*60*60*24 for days. toISOString() returns the ISO 8601 format.

setTimeout and setInterval
// setTimeout: runs once after a delay
const timer = setTimeout(() => {
  console.log("2s passed");
}, 2000);

// Cancel
clearTimeout(timer);

// setInterval: repeats every X ms
const interval = setInterval(() => {
  console.log("Tick");
}, 1000);

// Stop
clearInterval(interval);

// Countdown
let seg = 5;
const countdown = setInterval(() => {
  console.log(seg--);
  if (seg < 0) clearInterval(countdown);
}, 1000);

// requestAnimationFrame (60fps)
function animate() {
  // ... draw ...
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

setTimeout() runs once after a delay. setInterval() repeats. Both return an ID to cancel with clearTimeout()/clearInterval(). Use requestAnimationFrame() for animations.

Intl.DateTimeFormat and Relative Time
// Intl.DateTimeFormat (reusable)
const fmt = new Intl.DateTimeFormat("pt-PT", {
  weekday: "long",
  day: "numeric",
  month: "long",
  year: "numeric"
});
fmt.format(new Date());
// "Friday, 15 de March de 2024"

// RelativeTimeFormat ("3 days ago")
const rtf = new Intl.RelativeTimeFormat("pt", {
  numeric: "auto"
});
rtf.format(-3, "day");    // "3 days ago"
rtf.format(1, "month");   // "next month"
rtf.format(-2, "hour");   // "há 2 hours"

// Relative difference
function relativeTime(data) {
  const diff = data - Date.now();
  const days = Math.round(diff / 86400000);
  return rtf.format(days, "day");
}

Intl.DateTimeFormat is reusable and faster than toLocaleDateString() in loops. Intl.RelativeTimeFormat creates texts like "3 days ago" or "next month". It supports all locales.

DOM and Events


9 cards
Selecting Elements
// One element
const el = document.getElementById("app");
const el2 = document.querySelector(".card");
const el3 = document.querySelector("#app > p");

// Multiple elements
const all = document.querySelectorAll(".item");
const porClasse = document.getElementsByClassName("item");
const porTag = document.getElementsByTagName("p");

// querySelectorAll returns a NodeList (static)
// getElementsBy* returns an HTMLCollection (live)
all.forEach(el => console.log(el));

querySelector() accepts any CSS selector and returns the first match. querySelectorAll() returns all of them the a NodeList (has forEach). getElementsBy* returns an HTMLCollection (updates in real time).

Event Delegation
// Instead of one listener per button...
const list = document.querySelector("#list");

// One listener on the parent (delegation)
list.addEventListener("click", (e) => {
  const item = e.target.closest("li");
  if (!item) return;
  
  console.log("Clicked on:", item.textContent);
  
  // Example: remove item
  if (e.target.matches(".remove")) {
    item.remove();
  }
});

// Works for future (dynamic) elements
const novoLi = document.createElement("li");
novoLi.innerHTML = 'New <button class="remove">X</button>';
list.appendChild(novoLi);  // already has a listener!

Delegation: one listener on the parent captures events from children via bubbling. Use e.target.closest() to find the intended element. It works for dynamically added elements without registering new listeners.

MutationObserver
// Observe changes in the DOM
const observer = new MutationObserver((mutations) => {
  mutations.forEach(m => {
    if (m.type === "childList") {
      console.log("Children changed:", m.addedNodes.length);
    }
    if (m.type === "attributes") {
      console.log(`Attribute ${m.attributeName} changed`);
    }
  });
});

// Configure what to observe
observer.observe(document.body, {
  childList: true,      // adding/removing children
  attributes: true,     // attribute changes
  subtree: true,        // include descendants
  characterData: true   // text changes
});

// Stop observing
observer.disconnect();

MutationObserver detects changes in the DOM (children, attributes, text). It replaces the old MutationEvent (more performant). Useful for WYSIWYG editors, auto-save and change detection.

Creating and Inserting Elements
// Create
const div = document.createElement("div");
div.textContent = "Hello";
div.classList.add("card");
div.id = "new";

// Insert
const pai = document.querySelector("#app");
pai.appendChild(div);          // last child
pai.prepend(div);              // first child
pai.append(div, "text");      // multiple

// Insert relative to another element
const ref = document.querySelector(".ref");
ref.before(div);   // before ref
ref.after(div);    // after ref
ref.replaceWith(div);  // replaces ref

// Remove
div.remove();

createElement() creates the element, appendChild()/prepend() insert it into the parent. before()/after() insert relative to an element. remove() removes it from the DOM.

innerHTML vs textContent
const div = document.querySelector("#app");

// textContent: text only (safe)
div.textContent = "<b>Bold</b>";
// Shows: <b>Bold</b> (literal)

// innerHTML: parses HTML (beware XSS!)
div.innerHTML = "<b>Bold</b>";
// Shows: Bold (formatted)

// insertAdjacentHTML (doesn't clear children)
div.insertAdjacentHTML("beforeend", "<p>End</p>");
div.insertAdjacentHTML("afterbegin", "<p>Home</p>");

// Performance: DocumentFragment
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement("li");
  li.textContent = i;
  frag.appendChild(li);
}
div.appendChild(frag);  // 1 reflow

textContent is safe (doesn't parse HTML). innerHTML parses but is vulnerable to XSS. insertAdjacentHTML() inserts without clearing. DocumentFragment avoids multiple reflows.

Manipulating Classes and Styles
const el = document.querySelector(".card");

// classList
el.classList.add("active", "visible");
el.classList.remove("hidden");
el.classList.toggle("dark");
el.classList.contains("active");  // true
el.classList.replace("old", "new");

// Inline styles
el.style.color = "red";
el.style.backgroundColor = "#333";
el.style.cssText = "color: red; font-size: 16px;";

// Read computed styles
const styles = getComputedStyle(el);
styles.fontSize;  // "16px"

// Attributes
el.setAttribute("data-id", "42");
el.getAttribute("data-id");  // "42"
el.dataset.id;  // "42" (data-*)

classList manages classes without overwriting. toggle() adds/removes. style sets inline styles. getComputedStyle() reads the final CSS. dataset accesses data-* attributes.

Forms and Inputs
const form = document.querySelector("form");
const input = document.querySelector("#name");

// Read values
input.value;           // input text
input.checked;         // checkbox/radio
input.files;           // file input

// FormData (all fields)
const data = new FormData(form);
data.get("name");
data.append("extra", "value");

// Submit with fetch
form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const data = new FormData(form);
  const res = await fetch("/api", {
    method: "POST",
    body: data
  });
});

// Native validation
input.validity.valid;       // true/false
input.checkValidity();      // fires invalid
input.setCustomValidity("Error!");

FormData collects all the form fields. Use it with fetch() to submit. e.preventDefault() prevents the default submit. input.validity gives access to the browser's native validation.

Events — addEventListener
const btn = document.querySelector("#btn");

// Add a listener
btn.addEventListener("click", function(e) {
  console.log(e.target);       // clicked element
  console.log(e.type);         // "click"
  console.log(e.clientX);      // X position
  e.preventDefault();          // prevents default action
  e.stopPropagation();         // prevents bubbling
});

// Arrow function
btn.addEventListener("click", (e) => {
  console.log("clicked!");
});

// Options
btn.addEventListener("click", handler, {
  once: true,       // removed after 1st run
  capture: true,    // capture phase
  passive: true     // doesn't call preventDefault
});

// Remove
btn.removeEventListener("click", handler);

addEventListener() registers a handler. e.target is the element that originated the event. preventDefault() prevents the default action. once: true auto-removes after the first run.

IntersectionObserver (Lazy Load)
// Observe elements entering the viewport
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add("visible");
      observer.unobserve(entry.target);
    }
  });
}, {
  threshold: 0.1,     // 10% visible
  rootMargin: "50px"  // extra margin
});

// Observe elements
document.querySelectorAll(".lazy")
  .forEach(el => observer.observe(el));

// Lazy load images
const imgObs = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      imgObs.unobserve(img);
    }
  });
});

IntersectionObserver detects when elements enter the viewport. Ideal for lazy loading, scroll animations and infinite scroll. threshold sets the visible % to trigger. unobserve() stops observing.

Async e Promises


11 cards
Promises — Basics
// Create a Promise
const promessa = new Promise((resolve, reject) => {
  const success = true;
  if (success) {
    resolve("Data ready");
  } else {
    reject(new Error("Failed"));
  }
});

// Consume it
promessa
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs"));

// States: pending → fulfilled | rejected
// A Promise resolves only once

A Promise has 3 states: pending, fulfilled, rejected. resolve() fulfills, reject() rejects. .then() handles success, .catch() errors, .finally() always runs.

Promise.all and allSettled
// Run in parallel
const [users, posts] = await Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json())
]);

// allSettled: waits for all (even on error)
const results = await Promise.allSettled([
  fetch("/api/a"),
  fetch("/api/b")  // may fail
]);
results.forEach(r => {
  if (r.status === "fulfilled") console.log(r.value);
  else console.log(r.reason);
});

Promise.all() waits for all and fails if one fails. Promise.allSettled() waits for all without failing — it returns each one's status. Use all() when all are required.

Web Workers (Basics)
// worker.js (separate file)
self.onmessage = (e) => {
  const result = e.data * 2;
  self.postMessage(result);
};

// main.js
const worker = new Worker("worker.js");

worker.onmessage = (e) => {
  console.log("Result:", e.data);
};

worker.postMessage(21);  // sends data

// Terminate
worker.terminate();

// Inline worker (no file)
const blob = new Blob([`
  self.onmessage = (e) => {
    self.postMessage(e.data.toUpperCase());
  };
`], { type: "application/javascript" });
const inline = new Worker(URL.createObjectURL(blob));

Web Workers run code on a separate thread (doesn't block the UI). Communicate via postMessage()/onmessage. Ideal for heavy computations. terminate() ends the worker.

async/await
// An async function returns a Promise
async function fetchData() {
  try {
    const res = await fetch("/api/data");
    const data = await res.json();
    return data;
  } catch (error) {
    console.error("Error:", error);
    throw error;
  }
}

// Call it
fetchData()
  .then(data => console.log(data))
  .catch(e => console.error(e));

// In arrow functions
const fetch = async () => {
  const res = await fetch("/api");
  return res.json();
};

// Async IIFE
(async () => {
  const data = await fetch();
  console.log(data);
})();

async marks the function the asynchronous (returns a Promise). await pauses until the Promise resolves. Use try/catch for errors. You can only use await inside async functions (or top-level in modules).

Promise.race and any
// race: first to resolve (or reject)
const fastest = await Promise.race([
  fetch("/api/server1"),
  fetch("/api/server2")
]);

// any: first to succeed (ignores errors)
const first = await Promise.any([p1, p2, p3]);

// Timeout with race
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("Timeout")), ms)
  );
  return Promise.race([promise, timeout]);
}
await withTimeout(fetch("/api"), 5000);

Promise.race() returns the first to resolve OR reject. Promise.any() returns the first to succeed (ignores errors). Useful for timeouts and trying multiple servers.

Event Loop and Microtasks
console.log("1 - Sync");

setTimeout(() => {
  console.log("2 - Macrotask (timeout)");
}, 0);

Promise.resolve().then(() => {
  console.log("3 - Microtask (promise)");
});

queueMicrotask(() => {
  console.log("4 - Microtask (queue)");
});

console.log("5 - Sync");

// Order: 1, 5, 3, 4, 2
// Sync → Microtasks → Macrotasks

The Event Loop processes: 1) synchronous code, 2) microtasks (Promises, queueMicrotask), 3) macrotasks (setTimeout, events). Even with delay 0, setTimeout runs last.

fetch — GET and Reading
// Simple GET
const res = await fetch("https://api.example.com/data");
const data = await res.json();

// Check status
if (!res.ok) {
  throw new Error(`HTTP ${res.status}`);
}

// Headers and status
res.headers.get("content-type");
res.status;  // 200, 404, 500...

// DELETE
await fetch("/api/1", { method: "DELETE" });

fetch() returns a Promise. res.json() parses the body. Always check res.ok (status 200-299) before using the data. res.status gives the HTTP code.

Promise Chaining
// Chain .then()
fetch("/api/user/1")
  .then(res => res.json())
  .then(user => fetch(`/api/posts/${user.id}`))
  .then(res => res.json())
  .then(posts => console.log(posts))
  .catch(err => console.error(err));

// Equivalent with async/await (more readable)
async function getPosts() {
  const res = await fetch("/api/user/1");
  const user = await res.json();
  const res2 = await fetch(`/api/posts/${user.id}`);
  return res2.json();
}

// Returning a value vs a Promise
Promise.resolve(42)
  .then(n => n * 2)       // 84
  .then(n => Promise.resolve(n + 1))  // 85
  .then(console.log);

Each .then() returns a new Promise. You can return a value or another Promise. async/await is more readable for sequential operations. Errors propagate up to .catch().

Async Iterators (for await...of)
// Object with an async iterator
const pages = {
  async *[Symbol.asyncIterator]() {
    let page = 1;
    while (true) {
      const res = await fetch(`/api?page=${page}`);
      const data = await res.json();
      if (!data.length) return;
      yield data;
      page++;
    }
  }
};

// Consume with for await...of
for await (const items of pages) {
  console.log("Page:", items);
}

// Stream a fetch response
const res = await fetch("/api/big");
const reader = res.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log("Chunk:", value.length, "bytes");
}

for await...of iterates over async iterables. async *[Symbol.asyncIterator]() defines an asynchronous generator. Ideal for pagination, streams and reading files in chunks.

fetch — POST and Headers
// POST with JSON
const res = await fetch("/api/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token123"
  },
  body: JSON.stringify({ name: "Anna", age: 30 })
});

const created = await res.json();

// PUT (update)
await fetch("/api/1", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Mary" })
});

// POST with FormData (upload)
const form = new FormData();
form.append("file", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: form });

For POST, set Content-Type and use JSON.stringify() in the body. Authorization sends tokens. For uploads, use FormData without setting the header (the browser sets it automatically).

AbortController (Cancel fetch)
const controller = new AbortController();
const { signal } = controller;

// Fetch with signal
const res = await fetch("/api/data", { signal });

// Cancel after 5s
const timeout = setTimeout(() => {
  controller.abort();
}, 5000);

try {
  const data = await res.json();
  clearTimeout(timeout);
} catch (e) {
  if (e.name === "AbortError") {
    console.log("Request cancelled");
  }
}

// Cancel with a button
const btn = document.querySelector("#cancel");
btn.addEventListener("click", () => {
  controller.abort();
});

AbortController cancels fetch() requests. Pass signal in the options. controller.abort() cancels. The error has name === "AbortError". Useful for timeouts and cancelling requests when navigating.

ES6+ Modern


6 cards
Modules (import/export)
// utils.js — Named exports
export const PI = 3.14159;
export function sum(a, b) { return a + b; }
export class Calculator { /* ... */ }

// app.js — Named imports
import { PI, sum } from "./utils.js";
import { sum the add } from "./utils.js";  // alias
import * the utils from "./utils.js";  // namespace
utils.PI;  // 3.14159

// Default export (1 per file)
// api.js
export default class Api { /* ... */ }

// Import default (any name)
import Api from "./api.js";
import MyApi from "./api.js";  // same result

// Dynamic import (lazy loading)
const module = await import("./pesado.js");
module.func();

export exports, import imports. Named exports allow several per file. Default export only 1 per file. Dynamic import() loads on demand (code splitting). Use .js in the path.

WeakMap, WeakSet and Symbol
// Symbol: unique identifier
const id = Symbol("id");
const obj = { [id]: 123 };
obj[id];  // 123

// Well-known symbols
const arr = [3, 1, 2];
arr[Symbol.iterator]();  // iterator

// WeakMap: weak keys (GC can clean up)
const cache = new WeakMap();
function process(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const result = /* heavy computation */ obj;
  cache.set(obj, result);
  return result;
}

// WeakSet: unique objects (weak)
const seen = new WeakSet();
seen.add(element);
seen.has(element);  // true

Symbol creates unique identifiers. WeakMap/WeakSet have weak keys — the Garbage Collector can clean up entries if the key is collected. Ideal for caches and private metadata.

Classes
class Animal {
  // Private fields (ES2022)
  #energia = 100;
  
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }
  
  speak() {
    return `${this.name} makes ${this.sound}`;
  }
  
  // Getter/Setter
  get energia() { return this.#energia; }
  set energia(val) { this.#energia = Math.max(0, val); }
  
  // Static method
  static create(name) {
    return new Animal(name, "...");
  }
}

// Inheritance
class Dog extends Animal {
  constructor(name) {
    super(name, "Au au");
  }
  
  speak() {
    return `${super.speak()}!`;
  }
}

class defines classes. constructor() initializes. #field is private. extends/super() for inheritance. get/set create accessors. static belongs to the class, not the instance.

Top-level Await and using
// Top-level await (ES2022, in modules)
// No async IIFE needed!
const data = await fetch("/api/config")
  .then(r => r.json());

// Conditional dynamic import
const module = process.env.DEBUG
  ? await import("./debug.js")
  : await import("./production.js");

// using (ES2024 - resource management)
// using declares resources that are cleaned up automatically
function openFile() {
  return {
    [Symbol.dispose]() {
      console.log("File closed");
    }
  };
}

{
  using file = openFile();
  // use file...
}  // Symbol.dispose() called automatically

Top-level await allows await outside async functions (only in ES modules). using (ES2024) declares resources with automatic cleanup via Symbol.dispose() — similar to C#'s using or Python's with.

Generators and Iterators
// Generator function
function* counter(max) {
  let i = 0;
  while (i < max) {
    yield i++;
  }
}

const gen = counter(3);
gen.next();  // { value: 0, done: false }
gen.next();  // { value: 1, done: false }
gen.next();  // { value: 2, done: false }
gen.next();  // { value: undefined, done: true }

// Iterable with for...of
for (const n of counter(5)) {
  console.log(n);  // 0, 1, 2, 3, 4
}

// Lazy evaluation (infinite)
function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

function* creates a generator. yield pauses and returns a value. .next() resumes. They are lazy — they only compute when asked. Ideal for infinite sequences and data pipelines.

Proxy and Reflect
// Proxy: intercepts operations
const handler = {
  get(target, prop) {
    console.log(`Accessed: ${prop}`);
    return Reflect.get(target, prop);
  },
  set(target, prop, value) {
    if (typeof value !== "number") {
      throw new TypeError("Numbers only!");
    }
    return Reflect.set(target, prop, value);
  }
};

const data = new Proxy({ x: 1 }, handler);
data.x;      // log + 1
data.x = 5;  // OK
data.x = "a";  // TypeError!

// Reactive validation
const state = new Proxy({ count: 0 }, {
  set(t, p, v) {
    Reflect.set(t, p, v);
    render();  // re-renders
    return true;
  }
});

Proxy intercepts operations (get, set, delete, etc.) on an object. Reflect provides the standard methods. Useful for validation, logging, reactivity and data binding. The foundation of frameworks like Vue.

Errors and Debug


6 cards
try/catch/finally
try {
  const data = JSON.parse(text);
  if (!data.name) {
    throw new Error("Field 'name' is required");
  }
} catch (error) {
  console.error("Type:", error.name);     // "Error"
  console.error("Msg:", error.message);   // message
  console.error("Stack:", error.stack);   // trace
} finally {
  console.log("Always runs");
  // Close connections, clean up resources
}

// In async/await
async function load() {
  try {
    const res = await fetch("/api");
    return await res.json();
  } catch (e) {
    console.error("Failed:", e.message);
    return null;
  }
}

try runs risky code. catch captures errors. finally always runs (cleanup). throw new Error() throws custom errors. In async, use try/catch instead of .catch().

Global Error Handling
// Uncaught errors
window.addEventListener("error", (e) => {
  console.error("Global error:", e.message);
  console.error("File:", e.filename);
  console.error("Line:", e.lineno);
  // Send to a logging service
});

// Rejected Promises without catch
window.addEventListener("unhandledrejection", (e) => {
  console.error("Rejected Promise:", e.reason);
  e.preventDefault();  // don't show in console
});

// Error boundary in components (React-like)
class ErrorBoundary {
  static capture(fn, onError) {
    try { return fn(); }
    catch (e) { onError(e); return null; }
  }
}

window.onerror captures unhandled errors. unhandledrejection captures Promises without .catch(). Useful for global logging and reporting errors to services like Sentry. Doesn't replace local try/catch.

Error Types
// TypeError: wrong type
null.toString();        // TypeError
"str"();               // TypeError

// ReferenceError: variable doesn't exist
console.log(x);        // ReferenceError

// SyntaxError: invalid code
eval("if (");           // SyntaxError

// RangeError: value out of range
new Array(-1);          // RangeError
(1).toFixed(101);       // RangeError

// URIError: invalid URI
decodeURIComponent("%");  // URIError

// Custom error
class ValidationError extends Error {
  constructor(field, msg) {
    super(msg);
    this.name = "ValidationError";
    this.field = field;
  }
}
throw new ValidationError("email", "Invalid");

Native types: TypeError (wrong type), ReferenceError (doesn't exist), SyntaxError (invalid code), RangeError (out of range). Create classes with extends Error for custom errors.

AggregateError and Error Cause
// Error cause (ES2022)
try {
  await fetch("/api");
} catch (e) {
  throw new Error("Failed to load data", { cause: e });
}
// error.cause contains the original error

// AggregateError: group multiple errors
const errors = [
  new Error("Field name is required"),
  new Error("Invalid email"),
  new Error("Minimum age: 18")
];

throw new AggregateError(errors, "Validation failed");

// Promise.any throws AggregateError if all fail
try {
  await Promise.any([p1, p2, p3]);
} catch (e) {
  e.errors;  // array of all errors
}

Error cause ({ cause: e }) chains errors while keeping the original. AggregateError groups multiple errors into one. Promise.any() throws AggregateError when all promises fail.

Error Boundary (Pattern)
// Generic wrapper for risky operations
function try(fn, fallback = null) {
  try {
    return { data: fn(), error: null };
  } catch (e) {
    return { data: fallback, error: e };
  }
}

// Usage
const { data, error } = try(() => {
  return JSON.parse(textoInvalido);
});

if (error) {
  console.error("Parse failed:", error.message);
}

// Async version
async function tentarAsync(fn, fallback = null) {
  try {
    return { data: await fn(), error: null };
  } catch (e) {
    return { data: fallback, error: e };
  }
}

const { data: user } = await tentarAsync(
  () => fetch("/api/user").then(r => r.json())
);

Encapsulated try/catch pattern: returns { data, error } instead of throwing. Avoids repetitive try/catch. Inspired by the Result pattern from functional languages. Async version for Promises.

Debugging with DevTools
// debugger: pauses execution in DevTools
function calculate(x) {
  debugger;  // manual breakpoint
  return x * 2;
}

// console.dir: see object structure
console.dir(document.body);

// Monitor calls
const obj = { method() {} };
console.log(obj.method.toString());

// Performance
performance.now();  // precise timestamp
performance.mark("start");
// ... code ...
performance.mark("end");
performance.measure("op", "start", "end");

// See measurements
performance.getEntriesByType("measure");

// Memory
// Chrome DevTools > Memory > Heap Snapshot

debugger; creates a breakpoint in the code. performance.now() gives precise timestamps. performance.mark()/measure() measure sections. Use DevTools for conditional breakpoints and watch expressions.

Tricks and Tips


9 cards
Destructuring with Defaults
// Config with defaults
function criarServer({
  host = "localhost",
  port = 3000,
  ssl = false,
  db = { host: "localhost", port: 5432 }
} = {}) {
  console.log(`${host}:${port}`);
}

criarServer();  // localhost:3000
criarServer({ port: 8080, ssl: true });

// Optional parameters with an object
function fetch({ query = "", page = 1, limit = 10 } = {}) {
  return `/api?q=${query}&p=${page}&l=${limit}`;
}

// Extract with defaults
const { name = "Anonymous", role = "user" } = getUser();

Configuration pattern: an object with defaults + = {} to allow calling without arguments. More readable than positional parameters. Ideal for functions with many optional options.

Chaining and Immutability
// DON'T mutate — create new
const state = { items: [1, 2, 3], filter: "all" };

// Add an item
const new = { ...state, items: [...state.items, 4] };

// Remove an item
const sem2 = {
  ...state,
  items: state.items.filter(i => i !== 2)
};

// Update an item
const updated = {
  ...state,
  items: state.items.map(i =>
    i === 2 ? { ...i, done: true } : i
  )
};

// toSorted, toReversed, toSpliced (ES2023)
const sorted = [3,1,2].toSorted();  // doesn't mutate!
const inverted = [1,2,3].toReversed();
const withoutFirst = [1,2,3].toSpliced(0, 1);

Immutability: create new objects/arrays instead of mutating. spread + map/filter for transformations. ES2023: toSorted(), toReversed(), toSpliced() don't mutate the original.

URLSearchParams and Query Strings
// Read the current query string
const params = new URLSearchParams(window.location.search);
params.get("page");     // "2"
params.has("filter");   // true/false
params.getAll("tag");   // ["js", "web"]

// Create and modify
const p = new URLSearchParams();
p.set("q", "javascript");
p.set("page", "1");
p.append("tag", "es6");
p.toString();  // "q=javascript&page=1&tag=es6"

// Update the URL without reload
const url = new URL(window.location);
url.searchParams.set("page", "2");
history.pushState({}, "", url);

// Parse a full URL
const u = new URL("https://api.com/data?page=2&limit=10");
u.hostname;     // "api.com"
u.pathname;     // "/data"
u.searchParams.get("page");  // "2"

URLSearchParams manipulates query strings easily. history.pushState() updates the URL without reloading. new URL() parses full URLs. Essential for filters, pagination and sharing state via URL.

Useful Array Operations
// Last element
const last = arr.at(-1);

// Remove duplicates
const unico = [...new Set(arr)];

// Flatten
[[1,2],[3,4]].flat();       // [1,2,3,4]
[[1,[2,[3]]]].flat(Infinity);  // [1,2,3]

// Group (ES2024)
const items = [
  { type: "fruit", name: "apple" },
  { type: "legume", name: "cenoura" },
  { type: "fruit", name: "pear" }
];
const groups = Object.groupBy(items, i => i.type);

// Chunk (split into groups)
function chunk(arr, size) {
  return Array.from({ length: Math.ceil(arr.length / size) },
    (_, i) => arr.slice(i * size, (i + 1) * size));
}
chunk([1,2,3,4,5], 2);  // [[1,2],[3,4],[5]]

.at(-1) accesses the last element. [...new Set()] removes duplicates. .flat(Infinity) flattens completely. Object.groupBy() groups by key. chunk() splits into fixed-size groups.

Sleep and Retry
// Sleep (async pause)
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
await sleep(1000);  // pauses 1s

// Retry with attempts
async function retry(fn, tentativas = 3) {
  for (let i = 0; i < tentativas; i++) {
    try { return await fn(); }
    catch (e) {
      if (i === tentativas - 1) throw e;
      await sleep(1000 * (i + 1));
    }
  }
}

// Usage
const data = await retry(() => fetch("/api").then(r => r.json()));

sleep() pauses async code without blocking the thread. retry() repeats operations that may fail with an increasing delay (backoff). Essential for unstable network calls.

Debounce and Throttle
// Debounce: waits X ms after the last call
function debounce(fn, delay = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

// Usage: search while typing
input.addEventListener("input", debounce((e) => {
  fetch(e.target.value);
}, 500));

// Throttle: at most once every X ms
function throttle(fn, limit = 200) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= limit) {
      last = now;
      fn(...args);
    }
  };
}

// Usage: scroll
window.addEventListener("scroll", throttle(() => {
  checkPosition();
}, 100));

Debounce waits X ms after the last call (search while typing). Throttle limits to 1 execution per X ms (scroll, resize). Both avoid excessive calls to heavy functions.

Memoize and Cache
// Memoize (result cache)
function memoize(fn) {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

// Usage: Fibonacci with cache
const fib = memoize((n) => n <= 1 ? n : fib(n-1) + fib(n-2));
fib(40);  // fast!

// Simple cache with Map
const cache = new Map();
async function getCached(url) {
  if (cache.has(url)) return cache.get(url);
  const res = await fetch(url).then(r => r.json());
  cache.set(url, res);
  return res;
}

memoize() stores results in a cache — ideal for pure functions with heavy computations. Use Map the a cache for repeated requests. Only works with deterministic functions (same input = same output).

Deep Clone and Comparison
// Modern deep clone
const original = { a: 1, b: { c: [1, 2] } };
const copy = structuredClone(original);
copy.b.c.push(3);
original.b.c;  // [1, 2] (unchanged!)

// JSON (doesn't copy functions, Date, undefined)
const copy2 = JSON.parse(JSON.stringify(original));

// Compare objects (shallow)
const a = { x: 1, y: 2 };
const b = { x: 1, y: 2 };
a === b;  // false (different references)

// Shallow compare
const isEqual = JSON.stringify(a) === JSON.stringify(b);

// structuredClone supports:
// Date, RegExp, Map, Set, ArrayBuffer, etc.

structuredClone() does a native deep clone (supports Date, Map, Set). JSON.parse(JSON.stringify()) is limited (loses functions, Date becomes a string). Objects are compared by reference, not value.

Regex — Essentials
// Test and extract
const email = "ana@example.com";
/^[\w.-]+@[\w.-]+\.\w+$/.test(email);  // true

// Capture groups
const match = "2024-01-15".match(/(\d{4})-(\d{2})-(\d{2})/);
match[1];  // "2024"
match[2];  // "01"
match[3];  // "15"

// Named groups
const { groups } = "2024-01-15".match(
  /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
);
groups.year;  // "2024"

// Replace with regex
"10.50".replace(/\./g, ",");  // "10,50"
"hello world".replace(/\b\w/g, c => c.toUpperCase());
// "Hello World"

// Split
"a,b,,c".split(/,+/);  // ["a","b","","c"]

.test() checks if it matches. .match() extracts groups with (). (?<name>) creates named groups. .replace() with /g replaces all. \d = digit, \w = word, + = 1 or more.