const, let — and not var
- Default to
const. Reach forletonly when you genuinely reassign the binding. - Both are block scoped: they exist only inside the nearest
{ }. varis function scoped and hoisted to the top of the function asundefined, which produces bugs that read like magic.
const rate = 0.18; // cannot be reassigned
let total = 0; // will be reassigned
total += 100;
var old = 1; // function scoped, hoisted — do not use it
const stops the binding being reassigned, not the value being changed. const list = []; list.push(1) is fine. list = [] is not.
The temporal dead zone
let and const are hoisted too, but they stay uninitialised until the declaration runs. That gap is the temporal dead zone, and the error it throws is far better than silently getting undefined.
console.log(a); // undefined — var is hoisted and initialised to undefined
var a = 1;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 1;
The seven primitives
Everything that is not a primitive is an object — including arrays, functions, dates and regular expressions.
| Type | Example | For |
|---|---|---|
string | "Painted" | Text. Immutable — every "change" makes a new one. |
number | 5, 1.5, -0.2 | All numbers. There is no separate integer type. |
boolean | true, false | Two values. |
null | null | Deliberately empty. You set it. |
undefined | undefined | Not set. The language sets it. |
bigint | 9007199254740993n | Integers beyond 2^53. Rare. |
symbol | Symbol("id") | A unique key. Rarer still. |
null versus undefined
Use null when you are saying "there is deliberately no value here". Leave undefined to the language.
let a; // undefined — declared, never assigned
const b = null; // null — deliberately nothing
obj.missing; // undefined — no such property
fn(); // undefined — a function with no return
typeof undefined; // "undefined"
typeof null; // "object" ← a bug from 1995, never fixable
Strings are immutable
Every string method returns a new string. If you do not keep the result, nothing happened — a mistake that silently does nothing, which is the hardest kind to see.
let name = "painted";
name.toUpperCase(); // "PAINTED" — returned, not stored
console.log(name); // still "painted"
name = name.toUpperCase(); // now it is
Naming
camelCasefor variables and functions,PascalCasefor classes and components,SCREAMING_SNAKEfor true constants.- Name a boolean so it reads as a question:
isOpen,hasError,canEdit. - Name a function for what it does:
formatPrice, notprice. - Short names are fine for a short life:
iin a loop,din a one-line map. A variable that lives for thirty lines needs a real name.
If you have to read the assignment to know what a variable holds, the name is wrong.
Declaring in the right place
// worse: one binding reused for two unrelated things
let value;
value = door.days;
// …forty lines…
value = user.name;
// better: two bindings, each where it is needed
const days = door.days;
// …
const name = user.name;