Values and Types
Advanced framing: you already read JavaScript fluently. This chapter covers the semantic sharp edges through closures and this.
Values and Scope
JavaScript has seven primitives (number, string, boolean, null, undefined, symbol, bigint) and everything else is an object. Primitives are immutable; binding them with const freezes the binding, not the value.
const a = { n: 1 };
a.n = 2; // fine: const freezes the binding
// a = {}; // TypeErrorvar is function-scoped and hoisted-initialized to undefined; let/const are lexically block-scoped with a temporal dead zone. Prefer let/const; var survives mainly in legacy code and its hoisting interaction with closures in loops.
Coercion and Equality
== performs abstract equality (coercion); === does not. Internal algorithms matter: Object.is distinguishes -0 from 0 and treats NaN as equal to itself — the correct primitive for algorithms like Array.prototype.indexOf on sentinel values.
0 == ''; // true
0 === ''; // false
NaN === NaN; // false
Object.is(NaN, NaN); // true
+0 === -0; // true
Object.is(+0, -0); // falseControl Flow and Functions
switch uses strict comparison with fall-through; for...in enumerates keys (including inherited enumerables), for...of iterates via the iterator protocol. Hoisting differs by form: declarations are hoisted wholly; expressions and arrows are not.
function makeCounter() {
let count = 0;
return function () { count += 1; return count; };
}
const next = makeCounter();
console.log(next()); // 1
console.log(next()); // 2The closure retains count — a live binding to the creating scope, which is also the main memory-retention hazard (see the memory chapter).
this Binding
this is resolved at call time, not definition time: implicit receiver (obj.m()), default (undefined in strict mode, global otherwise), explicit (call/apply/bind), and new binding. Arrow functions have no this — they close over it lexically, which makes them ideal callbacks and unusable as methods or constructors.
const g = {
name: 'Ada',
hi() { return `hi ${this.name}`; },
hiArrow: () => `hi ${globalThis.name}`, // wrong receiver
};
console.log(g.hi()); // hi AdaExercise
Why does the classic for (var i...) + setTimeout loop log the final value for every callback, while let logs increasing values?
Show answer
var i is one function-scoped binding shared by every closure; let creates a fresh per-iteration binding, so each closure captures its own copy.
Spotted a mistake, or code that does not work?