JS Fundamentals Cheat Sheet

1. var / let / const

var is function-scoped and is legacy. You can redeclare vars. Hoisting applies and when hoisted, it initalizes to undefined, so referencing it early gives you undefined instead of an error.

let and const are block-scoped, ES6 (2015). Both hoist but stay uninitialized. Redeclaring throws an error “Identifier … has already been declared”.

const binds the name, not the value. The object is still mutable under the hood.

const cfg = { debug: true };
cfg.debug = false;  // fine
cfg = {};           // TypeError


2. Temporal Dead Zone

The stretch from the top of the scope down to where the variable or constant is declared. The binding exists but is uninitialized. If you use it before you declare it, it will throw a ReferenceError.

console.log(x);  // ReferenceError 
let x = 5;       // TDZ ends here where declare & assign

var hoists the same way but gets initialized to undefined immediately, and so it will fail quietly.


3. Arrow vs regular functions

Arrows functions don’t have their own this. A regular function’s this depends on how it’s called. An arrow uses the this from where it was written.

setInterval(function () { this.count++ }, 1000);  // wrong this
setInterval(() => { this.count++ }, 1000);        // works

Also true of arrows:

• no arguments

• no new and so can’t be constructors

• don’t get call-time bindings

• Often used with implicit returns (no curly braces)

No curly braces means implicit return. Add braces and you need return explicitly set.

Variable scope is identical in both.


4. == vs ===

=== is strict. Same type, same value, no conversion.
== coerces first, using a fixed type-pair table. Not left-wins.

1 === "1"      // false
1 ==  "1"      // true
"1" == true    // true, boolean goes to number first

Objects compare by reference under both. {a:1} === {a:1} is false and so is ==. No deep-equality operator exists in the language.

The one deliberate ==: x == null catches null and undefined and nothing else.


5. Spread, rest, destructuring

... is used as either spread or rest. It is a spread when you see it on the right side of an assignment; it is a rest when you see it on the left side of an assignment or as part of the parameters of a function declaration.


const copy = { ...obj };     // spread, expands
const { a, ...rest } = obj;  // rest, gathers

Spread is a shallow copy. Nested objects are shared by reference.

const copy = { ...orig };
copy.config.debug = false;   // also changes orig

Deep copy: structuredClone(obj). The JSON.parse(JSON.stringify()) trick drops Date, undefined, functions, Map, Set.

Destructuring unwraps a package (object), pulls the values out, assigns them to the named variables on the left side of the assignment.

let obj = {a: "hello", b: "world"} 
const { a, b } = obj; // destructuring

This works because the names of the variables on the left match the names of the properties inside the object.


6. Closures

A function that keeps access to the variables from the scope where it was defined, even after that scope has returned. Those variables stay alive because the inner function still points at them.

Used for lots of patterns:

• event handlers

• callbacks

• partial application pattern

• hooks

• storing state without needing classes

function counter() {
  let count = 0;
  return () => ++count;
}
const next = counter();
next();  // 1
next();  // 2

7. null vs undefined

null is intentional. A programmer assigned it to mean “empty on purpose.”

undefined is the default when nothing was assigned: a declared but unassigned variable, a missing property, a missing argument, or a function with no return.

A variable that was never declared doesn’t give undefined. It throws a ReferenceError.

typeof null is "object". That’s a historical bug in the language.


8. ?? vs ||

?? falls back only when the left side is null or undefined.
|| falls back when the left side is any falsy value, including 0, "", false, and NaN.

const count = 0;
count || 10;  // 10, probably a bug
count ?? 10;  // 0

Use ?? for defaults when 0 or "" are valid values.


9. async / await

An async function always returns a promise, even if you return a plain value.

await pauses that function until a promise settles. The rest of the program keeps running.

async function load() {
  const res = await fetch(url);  // load() pauses here
  return res.json();
}

await works only inside an async function or at the top level of a module.


10. Promise.all vs Promise.allSettled

Promise.all rejects as soon as any one promise rejects. The others keep running. Promises can’t be cancelled. all just ignores their results.

Promise.allSettled waits for all of them and never rejects. You get an array of { status: "fulfilled", value } or { status: "rejected", reason }.

Use allSettled when partial failure is acceptable.


11. Event loop order

console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
// A, D, C, B

Synchronous code runs first. Then the microtask queue drains, which holds promise callbacks. Then the next task runs, which holds setTimeout callbacks.

A .then never runs inline, even on an already-resolved promise.


12. Optional chaining ?.

If the thing on the left is null or undefined, the expression stops and returns undefined instead of throwing.

user?.address?.city
obj?.[key]
callback?.()

A missing property already returns undefined without ?.. The operator protects you when the object itself is missing.


13. Map vs plain object

  • Map keys can be any type. Object keys are strings or symbols.
  • Map has .size. Objects need Object.keys(obj).length.
  • Map iterates directly, in insertion order.
  • Map has no prototype keys, so no collisions with names like constructor.
  • Map performs better with frequent adds and deletes.

14. reduce

Walks an array and carries an accumulator forward, ending with a single value.

[1, 2, 3].reduce((acc, cur) => acc + cur, 0);  // 6

Always pass the initial value. Without it, an empty array throws.


15. Event delegation

Put one listener on a parent. Events from children bubble up to it. Check event.target to see which child fired.

list.addEventListener("click", (e) => {
  if (e.target.matches("li")) handle(e.target);
});

Fewer listeners, and it works for children added later.


16. map vs forEach

map returns a new array of transformed values. forEach returns undefined and is for side effects.

Neither mutates the array on its own. Neither can be broken out of. If you need break, use for...of.

Lowercase map is the array method. Capital Map is the data structure.

By Jason

Leave a Reply

Your email address will not be published. Required fields are marked *