Programming foundations: JavaScript

Sefism.

Chapter 03

JavaScript essentials

The parts of JavaScript that confuse people for years if skipped: declarations, equality, closures, destructuring, this and asynchronous code.

let, const and var

Also called block scope, hoisting

What
const declares a binding that cannot be reassigned, let one that can, and both are scoped to the block. var is the old function-scoped form and is hoisted.
Why
Default to const, use let when you must reassign, and never var: block scoping removes a family of loop and closure bugs.
JavaScript
const user = { name: 'Ada' };
user.name = 'Grace';
let count = 0;
count += 1;
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i));
// prints 3 3 3; with let it prints 0 1 2

Watch out

Thinking const makes an object immutable: you cannot reassign the name, but you can still change its properties.

=== and ==

Also called strict equality, type coercion

What
=== compares value and type with no conversion. == converts types first by rules few people remember, so 0 == '' is true.
Why
Always use === and !== so comparisons mean what they say. Linters flag == for this reason.
JavaScript
console.log(0 == '');            // true
console.log(null == undefined);   // true
console.log(0 === '');           // false
console.log(NaN === NaN);        // false
console.log(Number.isNaN(NaN));  // true

Watch out

Testing for NaN with ===: NaN is not equal to itself, so use Number.isNaN.

Closures

Also called lexical scope

What
A closure is a function that remembers the variables of the scope it was created in, even after that outer function has returned.
Why
Closures power callbacks, event handlers, private state and factories. React hooks and most async code rely on them.
JavaScript
function makeCounter() {
  let count = 0;
  return () => {
    count += 1;
    return count;
  };
}
const next = makeCounter();
next();
console.log(next()); // 2

Watch out

A callback that captured an old value of a variable (a stale closure) keeps using it after the variable has changed.

Destructuring and spread

Also called rest parameters, spread operator, ...

What
Destructuring pulls values out of arrays and objects into variables in one statement. The ... syntax spreads items into a new array or object, or collects the rest.
Why
It makes function options, copying with changes and working with API responses short and readable.
JavaScript
const user = { name: 'Ada', role: 'admin', city: 'London' };
const { name, ...rest } = user;
const [first, second = 'none'] = ['a'];
const moved = { ...user, city: 'Paris' };

function greet({ name, greeting = 'Hi' }) {
  return `${greeting}, ${name}`;
}

Watch out

Expecting spread to deep copy: nested objects are still shared, so use structuredClone for a deep copy.

The event loop

Also called call stack, microtasks, task queue

What
JavaScript runs your code on one thread. When the current code finishes, the event loop runs queued microtasks (promise callbacks) and then the next task (timers, I/O, events).
Why
It explains why a slow loop freezes a page or server, and why a setTimeout(fn, 0) callback runs after promise callbacks.
JavaScript
console.log('1');
setTimeout(() => console.log('4'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('2');

Watch out

Running heavy CPU work on the main thread, which blocks every other request or click until it finishes.

Promises and async/await

Also called async, await, Promise.all

What
A promise represents a value that will arrive later, or fail. await pauses an async function until the promise settles, so asynchronous code reads top to bottom.
Why
Network calls, file reads and timers are all asynchronous in JavaScript. Promise.all runs independent operations at the same time instead of one after another.
JavaScript
async function loadUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

async function loadAll(ids) {
  return Promise.all(ids.map((id) => loadUser(id)));
}

loadAll([1, 2]).then(console.log).catch(console.error);

Watch out

Forgetting await, so you get a pending promise instead of the data and errors go unhandled.

this

Also called bind, arrow functions

What
In a normal function, this is decided by how the function is called, not where it is written. Arrow functions have no this of their own and use the surrounding one.
Why
It explains methods that break when passed as callbacks, and why arrow functions are the usual choice inside methods.
JavaScript
const timer = {
  seconds: 0,
  start() {
    setInterval(() => { this.seconds += 1; }, 1000);
  },
};
timer.start();
const start = timer.start;
start(); // this is no longer timer

Watch out

Passing obj.method as a callback, which detaches it from obj so this is lost; use an arrow function or bind.

03 · JavaScript essentials3 / 10