Chapter 2 Β· Foundations Revisited

Grammar, Types & Declarations

let and const replaced var for good reasons. This chapter shows exactly what changed, including hoisting and the "temporal dead zone", and then tours JavaScript's eight data types and their literals.

You will learn

  • var vs let vs const: scope, re-declaration, and what "constant" really means.
  • Hoisting, and the temporal dead zone (TDZ) of let, const and class.
  • The seven primitive types plus objects, and the quirks of typeof.
  • Primitives vs references: what gets copied and what gets shared.
  • Every kind of literal, including BigInt and numeric separators.

A few grammar basics

❌ ASI trap: returns undefined

βœ… Keep the value on the same line

Declarations: var, let, const ES2015

varletconst
Scopefunction (or global)block { … }block { … }
Re-declare in the same scopeallowedSyntaxErrorSyntaxError
Re-assignyesyesno (TypeError)
Use before the declarationundefined (hoisted)ReferenceError (TDZ)ReferenceError (TDZ)
Creates a global property (in a classic script)yesnono

const means "constant binding", not "frozen value"

const stops you from re-assigning the name. If the value is an object, you can still change what's inside it. To make the object itself unchangeable, use Object.freeze, which is shallow: nested objects stay mutable.

(The assignment to a frozen property was silently ignored, because the REPL runs in sloppy mode. In a module it would throw a TypeError.)

πŸ’‘ Default to const, use let when you really re-assign, and don't use var in new code. Seeing let then tells the reader "this changes later".

β˜• Java: const is like final on a reference: final List<String> l can still be added to. JavaScript variables have no declared type; the value carries the type.

Hoisting and the temporal dead zone

Before running a scope, the engine registers every declaration in it. This is called hoisting. What a name holds before its declaration line runs depends on how it was declared:

⚠️ Pitfall: function expressions are not fully hoisted. const f = () => … is a const, so calling f() above that line throws. Only function name() {} declarations can be called early.

The TDZ is about time, not position: a function can mention a let variable declared below it, as long as the function is called after the declaration has run.

Data types

JavaScript has seven primitive types and objects. A primitive is an immutable value with no methods of its own. When you call "abc".toUpperCase(), JavaScript briefly wraps the string in a String object to find the method.

TypeExampletypeof
Undefinedundefined"undefined"
Nullnull"object" ⚠️ (a historic bug)
Booleantrue"boolean"
Number (64-bit float)42, 3.14, NaN, Infinity"number"
BigInt ES20209007199254740993n"bigint"
String"Dune""string"
Symbol ES2015Symbol("id")"symbol"
Object (incl. arrays, functions, dates, …){}, [], () => {}"object", or "function" for functions

πŸ’‘ Reliable type checks: x === null for null, Array.isArray(x) for arrays, Number.isNaN(x) for NaN, and x instanceof Date for class instances. typeof is best for primitives and functions.

undefined vs null

Both mean "no value", by convention in different ways. undefined is what the language gives you (a missing property, a missing argument, a function with no return). null is what programmers write on purpose to say "intentionally empty". Many codebases check for both with x == null, the one widely accepted use of ==, or with x ?? fallback (Chapter 5).

Symbols, in one paragraph

Every Symbol() call creates a unique value, so it can be used as an object key that never collides with string keys. The built-in "well-known" symbols such as Symbol.iterator let you hook into language features (Chapters 13 and 15).

Primitives vs references

Assigning a primitive copies the value. Assigning an object copies a reference to the same object, just as in Java.

Comparing objects with === compares identity: {} === {} is false. Chapter 5 covers equality in depth.

Literals

KindExamplesNotes
Numbers255, 0xff, 0o377 ES2015, 0b1111_1111, 2.5e3_ numeric separators ES2021
BigInt10n, 2n ** 64narbitrary size integers; can't mix with Number
Strings'a', "b", `c ${x}` ES2015Chapter 6
Arrays[1, 2, 3], [1, , 3]the second has a hole (Chapter 8)
Objects{ title, [key]: v, f() {} }shorthand and computed keys ES2015
Regular expressions/\d+/gChapter 7
Booleans, nulltrue, false, nullundefined is a global property, not a literal

More examples

1. Choosing the declaration

2. A type-describing helper

The order of the checks matters: null and arrays must be handled before falling back to typeof, which would call both of them "object".

3. Shadowing with block scope

4. Large IDs need BigInt

5. Freezing a settings object deeply

Recap

  • Use const by default and let when you re-assign. var is function-scoped, re-declarable and hoisted as undefined.
  • let, const and class are in the TDZ until their line runs. Function declarations are fully hoisted.
  • const freezes the binding, not the value. Object.freeze is shallow.
  • There are seven primitives plus objects. typeof null === "object", so use Array.isArray and Number.isNaN for those checks.
  • Primitives copy by value and objects by reference. structuredClone makes a deep copy.

Exercises

Exercise 1 🟒 easy predict the output

What do these give? typeof null, typeof NaN, typeof [], typeof 10n, typeof undefined

Show solution

"object", "number", "object", "bigint", "undefined". The first one is a bug from 1995 that can never be fixed without breaking the web.

Exercise 2 🟒 easy fix the bug

This throws TypeError: Assignment to constant variable. Fix it.

Show solution

count is re-assigned, so it must be let. const book in for…of is fine, because each iteration gets a fresh binding.

Exercise 3 🟒 easy explain why

Why does describe in "More examples" #2 check x === null and Array.isArray(x) before using typeof?

Show solution

Because typeof null and typeof [] are both "object", so typeof can't tell them apart from plain objects. The special cases have to run first; everything else is described well by typeof.

Exercise 4 🟒 easy predict the output

What does this print?

Show solution

number undefined. var i belongs to the surrounding scope and is 3 after the loop; let j only exists inside the loop.

Exercise 5 🟑 medium predict the output

What does each line print, or throw?

Show solution

The first prints undefined (a hoisted var). The second throws ReferenceError: Cannot access 'b' before initialization. Even typeof isn't safe inside the TDZ, although it is safe for names that were never declared at all.

Exercise 6 🟑 medium refactor it

Replace every var with let or const, choosing the most restrictive one that works.

Show solution

Only total and i are re-assigned. p is a new binding on each pass, so it can be const.

Exercise 7 🟑 medium build it · playground

Create playground/types.js with typeName(x), which returns one of null, array, nan, integer, float, bigint, string, boolean, symbol, function, undefined or object. Print it for a mixed sample.

Show solution

The special cases are checked first, and typeof handles the rest. Number.isInteger(3.0) is true, because JavaScript has a single number type.

Exercise 8 🟑 medium fix the bug

Changing draft also changes original. Fix it, so that nested arrays are copied too.

Show solution

Spread copies only the top level, so draft.tags is the same array object. structuredClone copies all the way down.

Exercise 9 πŸ”΄ hard predict the output

What does this print?

Show solution

ReferenceError. The inner let x is hoisted to the top of f's scope, so x inside f refers to the inner variable, which is still in its TDZ. It does not fall back to the outer "outer".

Exercise 10 πŸ”΄ hard mini-project Β· playground

Create playground/inventory.js. It holds a frozen CATALOG of books (id, title, price in cents) and a mutable stock object. Write restock(id, qty), which validates that qty is a positive safe integer and that the id exists, and a report() that prints each book with its stock. Show that the catalog can't be modified.

Show solution

This combines const with a deep freeze (each book frozen too), strict-mode errors on frozen writes, safe-integer validation, and destructuring in for…of (Chapter 5).

Official docs