JavaScript · Module 1 · learnJS.eu

Les Variables
en JavaScript

De var à const — portée, hoisting, TDZ, types, coercition. Les fondations que tout le monde utilise mais que peu comprennent vraiment.

10
Chapitres
40+
Exemples
4
Étapes projet
🏗️ Ce que tu vas construire Un générateur de carte de profil

Tu vas construire une carte de profil dynamique qui affiche les données d'un utilisateur en temps réel — nom, âge, ville — tout en visualisant les types JS de chaque valeur. C'est le projet fil rouge de ce cours.

1Déclarer les variables du profil (const / let)
2Détecter & afficher les types avec typeof
3Convertir & valider les données (coercition)
4Version finale — patterns & nommage pro
profil.js Live
Projet :
① Déclarations ② Types ③ Coercition ④ Patterns
01

Introduction aux variables

Une variable est une boîte étiquetée qui stocke une valeur en mémoire. En JavaScript, trois mots-clés permettent d'en créer : var, let et const — chacun avec des règles bien distinctes.

🎭
L'analogie du casier scolaire Imagine un couloir de lycée avec des casiers. const est un casier verrouillé avec un cadenas — tu peux y mettre des affaires, mais tu ne peux pas remplacer le casier lui-même. let est un casier ordinaire — tu peux vider et remplir à volonté. var est un casier sans porte dans un couloir partagé — tout le monde y a accès et tu sais jamais qui a touché à tes affaires.

var

Héritage ES1 (1995). Portée fonction, hoisting bizarre, re-déclaration silencieuse. À éviter dans tout code moderne.

let

ES6. Portée bloc, TDZ, pas de re-déclaration. Le choix par défaut pour les valeurs qui changent.

const

ES6. Portée bloc, liaison immuable (pas la valeur !). À utiliser par défaut — pas de réassignation.

javascript
// Les 3 façons de déclarer une variable
const nom   = "Alice";   // ← par défaut : const
let   score = 0;        // ← si la valeur va changer
var   age   = 30;       // ← à éviter

// const sans valeur → erreur immédiate
const x; // SyntaxError: Missing initializer

// let sans valeur → undefined
let y;   // y === undefined
02

var, let, const — les différences

var — pourquoi c'est problématique

var ignore les blocs {} et s'échappe dans la portée parente. Elle autorise aussi la re-déclaration silencieuse — un bug qui peut passer des heures sans être détecté.

javascript — les problèmes de var
// ① Portée fonction (pas bloc)
if (true) {
  var x = 10; // var ignore le bloc {}
}
console.log(x); // 10 ← visible ici !

// ② Re-déclaration silencieuse
var utilisateur = "Alice";
// ... 200 lignes plus tard ...
var utilisateur = "Bob"; // aucune erreur ! Alice écrasée silencieusement

// ③ Bug classique des boucles
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Affiche : 3, 3, 3  (pas 0, 1, 2 !)
// var i est partagée entre toutes les itérations

const — la liaison immuable

const crée une liaison immuable, pas une valeur immuable. On ne peut pas réassigner la variable, mais on peut muter l'objet ou tableau qu'elle référence.

javascript — const
const PI = 3.14;
PI = 3; // TypeError: Assignment to constant variable ✓

// ⚠️ const n'empêche PAS la mutation
const user = { nom: "Alice" };
user.nom = "Bob"; // ✓ OK — on mute l'objet, pas la liaison
user = {};        // TypeError — réassignation interdite

// Pour vraiment geler un objet :
const config = Object.freeze({ api: "https://..." });
config.api = "autre"; // silencieusement ignoré
📍 Projet · Étape 1 / 4 Déclarer les variables du profil

On déclare les données du profil en choisissant consciemment const ou let selon si la valeur peut changer.

profil.js — étape 1
// Données fixes → const
const NOM           = "Alice";
const DATE_NAISSANCE = "1996-04-12";

// Données variables → let
let score     = 0;
let estConnecte = false;
let ville     = "Paris";

// Objet profil complet → const (mutation autorisée)
const profil = {
  nom   : NOM,
  age   : 28,
  ville : ville,
};

console.log(profil); // { nom: "Alice", age: 28, ville: "Paris" }
💡
Règle d'or — const par défaut Commence toujours par const. Passe à let uniquement quand ton éditeur te signale que tu réassignes. Cette règle simple élimine 80% des bugs liés aux variables.
✦ CheckpointVérifie ta compréhension

Que se passe-t-il si on essaie d'ajouter une propriété à un objet déclaré avec const ?

03

Types de données

JavaScript est un langage à typage dynamique — une même variable peut contenir des types différents au cours de l'exécution. Il existe 7 types primitifs et 1 type objet.

javascript — les 7 primitifs
const txt  = "Bonjour";        // string
const n    = 42;               // number  (entiers ET flottants)
const big  = 9007199254740993n; // bigint
const ok   = true;             // boolean
const vide = null;             // null    (absence intentionnelle)
let   u;                       // undefined (non initialisé)
const id   = Symbol("id");     // symbol  (unique)

// Type object (tout le reste)
const obj  = { a: 1 };         // object
const arr  = [1, 2];           // object (tableau)
const fn   = () => {};          // function

typeof — inspecter le type

javascript — typeof
typeof "hello"      // "string"
typeof 42           // "number"
typeof true         // "boolean"
typeof undefined    // "undefined"
typeof {}           // "object"
typeof []           // "object"  ← tableau = objet
typeof null         // "object"  ← bug historique de JS !
typeof function(){}  // "function"

// Vérification fiable de null :
value === null

// Vérification fiable de tableau :
Array.isArray([]); // true ✓
📍 Projet · Étape 2 / 4 Afficher les types de chaque champ

On enrichit la carte de profil pour afficher le type JS de chaque valeur — comme un inspecteur de données en temps réel.

profil.js — étape 2
function afficherProfil(profil) {
  const champs = Object.entries(profil);

  return champs.map(([cle, valeur]) => `
    <div class="champ">
      <span class="cle">${cle}</span>
      <span class="valeur">${valeur}</span>
      <span class="type">typeof: ${typeof valeur}</span>
    </div>
  `).join('');
}

const profil = {
  nom       : "Alice",  // string
  age       : 28,      // number
  actif     : true,    // boolean
  telephone : null,    // object ← bug typeof !
};

document.getElementById('carte').innerHTML = afficherProfil(profil);
✦ CheckpointVérifie ta compréhension

Que retourne typeof null en JavaScript ?

04

Portée (Scope)

La portée définit où une variable est accessible. JavaScript a 4 niveaux : globale, module, fonction et bloc.

🎭
L'analogie des poupées russes Les scopes sont comme des poupées russes imbriquées. Depuis l'intérieur, tu vois tout ce qu'il y a dehors. Mais depuis l'extérieur, tu ne vois pas ce qu'il y a à l'intérieur. Une variable déclarée dans une fonction est invisible depuis le scope global — mais elle peut accéder aux variables globales.
javascript — les 4 niveaux de portée
// 1. Portée globale
const GLOBAL = "visible partout";

function externe() {
  // 2. Portée fonction
  const locale = "dans externe()";

  function interne() {
    // 3. Portée imbriquée — closure
    console.log(GLOBAL);  // ✓ accessible
    console.log(locale);  // ✓ accessible (closure)

    // 4. Portée bloc
    if (true) {
      let bloc = "seulement ici";
    }
    // console.log(bloc); // ReferenceError
  }
}

// console.log(locale); // ReferenceError — invisible dehors
05

Hoisting & Temporal Dead Zone

Le hoisting est le mécanisme par lequel JS déplace conceptuellement les déclarations en haut de leur portée avant l'exécution. Le comportement est très différent selon var, let/const et les fonctions.

javascript — hoisting
// var : hissée ET initialisée à undefined
console.log(x); // undefined (pas d'erreur !)
var x = 5;

// Fonctions : entièrement hissées
direBonjour(); // ✓ fonctionne avant la déclaration
function direBonjour() { console.log("Bonjour !"); }

// let / const : hissées mais PAS initialisées → TDZ
console.log(nom); // ReferenceError: Cannot access before initialization
let nom = "Alice"; // ← fin de la TDZ
Mot-cléHissé ?Initialisé ?Avant déclaration ?
var✅ Oui✅ undefined⚠️ Oui (undefined)
let✅ Oui❌ Non🚫 Non (TDZ)
const✅ Oui❌ Non🚫 Non (TDZ)
function✅ Oui✅ Complète✅ Oui
06

Coercition de types

JavaScript convertit automatiquement les types lors de certaines opérations — c'est la coercition implicite. Source de nombreux comportements surprenants.

javascript — coercition implicite
// + avec string = concaténation
"3" + 4       // "34"
1 + 2 + "3"  // "33" (1+2=3, puis 3+"3")

// Autres opérateurs = coercition numérique
"5" - 2       // 3
true + 1     // 2  (true → 1)
null + 1     // 1  (null → 0)

// == coerce, === ne coerce pas
"1" == 1     // true  ← dangereux
"1" === 1    // false ← correct
[] == false  // true  😱
[] == ![]    // true  😱😱

Conversion explicite — la bonne pratique

javascript — conversions explicites
// → Number
Number("42")       // 42
Number("abc")      // NaN
+"42"              // 42 (unary plus)
parseInt("42px")  // 42

// → String
String(42)          // "42"
(42).toString(2)  // "101010" (binaire)

// → Boolean
Boolean(0)          // false
Boolean("0")        // true ! (string non vide)
Boolean([])         // true ! (objet)
!!value             // double négation — idiomatique
📍 Projet · Étape 3 / 4 Valider et convertir les données du profil

On ajoute une fonction de validation qui s'assure que l'âge est bien un nombre et que le nom n'est pas vide — avec des conversions explicites.

profil.js — étape 3
function validerProfil({ nom, age, ville }) {
  const erreurs = [];

  // Nom : doit être une string non vide
  const nomStr = String(nom).trim();
  if (!nomStr) erreurs.push("Le nom est requis");

  // Âge : convertir et vérifier que c'est un entier valide
  const ageNum = Number(age);
  if (Number.isNaN(ageNum) || ageNum < 0 || ageNum > 150) {
    erreurs.push("L'âge doit être un nombre entre 0 et 150");
  }

  if (erreurs.length) throw new Error(erreurs.join(", "));

  return {
    nom : nomStr,
    age : Math.floor(ageNum),
    ville: String(ville).trim() || "Non renseigné",
  };
}
07

Nullish & Valeurs Falsy

Certaines valeurs sont considérées comme fausses dans un contexte booléen. Connaître la différence entre falsy et nullish évite de nombreux bugs subtils.

8 valeurs Falsy

false · 0 · -0 · 0n · "" · null · undefined · NaN

Tout le reste est Truthy

"0" · "false" · [] · {} · -1 · Infinity · toute fonction

javascript — || vs ??
// || valeur par défaut — attention aux falsy !
const nom  = "" || "Anonyme";  // "Anonyme" (car "" est falsy)
const port = 0 || 3000;       // 3000 — bug ! 0 est falsy

// ?? nullish coalescing — uniquement null/undefined
const nom2  = "" ?? "Anonyme"; // "" ← correct !
const port2 = 0 ?? 3000;      // 0  ← correct !

// ??= assigne seulement si null/undefined
let config = {};
config.debug ??= false;  // assigne seulement si absent
config.nom   ||= "défaut"; // assigne si falsy
📍 Projet · Étape 4 / 4 Profil final — nommage & patterns production

On assemble la version finale du profil en appliquant toutes les conventions de nommage et en utilisant ?? pour les valeurs optionnelles.

profil.js — étape 4 (finale)
// UPPER_SNAKE_CASE pour les constantes de config
const AGE_MAX         = 150;
const NOM_PAR_DEFAUT  = "Anonyme";

// camelCase pour les variables
const estConnecte  = true;  // booléen → préfixe "est"/"is"
const hasAvatar    = false; // booléen → préfixe "has"

// Profil final avec valeurs par défaut via ??
function creerProfil(data = {}) {
  return {
    nom    : (String(data.nom ?? "").trim() || NOM_PAR_DEFAUT),
    age    : Math.min(Math.abs(Number(data.age) || 0), AGE_MAX),
    ville  : String(data.ville ?? "Non renseigné").trim(),
    creeLe : new Date().toLocaleDateString(),
  };
}

const alice = creerProfil({ nom: "Alice", age: 28, ville: "Paris" });
console.log(alice); // { nom: "Alice", age: 28, ville: "Paris", creeLe: "..." }
08

Pièges classiques

🪤 Piège 1 — var dans une boucle & setTimeout

Avec var, toutes les itérations d'une boucle partagent la même variable i. Au moment où le callback s'exécute, la boucle est terminée et i vaut sa valeur finale.

javascript
// ❌ avec var — bug
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 3, 3, 3  ← var i est partagée

// ✅ avec let — chaque itération a son propre i
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 0, 1, 2  ✓
🪤 Piège 2 — 0.1 + 0.2 ≠ 0.3

JS utilise la norme IEEE 754 pour les flottants. Certaines valeurs décimales ne peuvent pas être représentées exactement en binaire, ce qui crée des imprécisions.

javascript
0.1 + 0.2 === 0.3  // false ! (0.30000000000000004)

// ✅ Comparer avec une tolérance (epsilon)
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON  // true ✓

// ✅ Pour de l'argent, utiliser des entiers
const prixCentimes = 1099; // 10,99€ en centimes
🪤 Piège 3 — NaN n'est pas égal à NaN

NaN est la seule valeur JS qui n'est pas égale à elle-même. Ne jamais tester x === NaN — utiliser Number.isNaN().

javascript
const x = Number("abc"); // NaN

x === NaN               // false ! toujours
x !== x                 // true  ← NaN est l'unique exception

// ✅ La bonne façon
Number.isNaN(x)          // true ✓
isNaN("abc")            // true mais coerce d'abord — éviter
Number.isNaN("abc")     // false ← plus fiable
09

Exercices

Exercice 1 Inspecter les types d'un tableau de valeurs

Écris une fonction inspecter(valeurs) qui prend un tableau et retourne un tableau d'objets { valeur, type } en utilisant typeof. Pour null, renvoie "null" (pas "object"). Pour les tableaux, renvoie "array".

solution
function inspecter(valeurs) {
  return valeurs.map(v => {
    let type;
    if (v === null)          type = "null";
    else if (Array.isArray(v)) type = "array";
    else                      type = typeof v;
    return { valeur: v, type };
  });
}

inspecter(["Alice", 42, true, null, undefined, [], {}]);
// [
//   { valeur: "Alice", type: "string" },
//   { valeur: 42,      type: "number" },
//   { valeur: true,    type: "boolean" },
//   { valeur: null,    type: "null" },      ← pas "object"
//   { valeur: undefined, type: "undefined" },
//   { valeur: [],      type: "array" },      ← pas "object"
//   { valeur: {},      type: "object" },
// ]
Exercice 2 Convertisseur de température

Crée un module de conversion de températures avec trois fonctions : celsiusVers(valeur, unite) qui convertit vers "F" ou "K", et normaliser(valeur) qui accepte une string ou un nombre et lève une erreur si la valeur est NaN après conversion.

solution
function normaliser(valeur) {
  const n = Number(valeur);
  if (Number.isNaN(n)) {
    throw new Error(`"${valeur}" n'est pas une température valide`);
  }
  return n;
}

function celsiusVers(valeur, unite) {
  const c = normaliser(valeur);
  const conversions = {
    F: c => c * 9/5 + 32,
    K: c => c + 273.15,
  };
  const fn = conversions[unite.toUpperCase()];
  if (!fn) throw new Error(`Unité inconnue : ${unite}`);
  return Math.round(fn(c) * 100) / 100;
}

celsiusVers(100, "F");   // 212
celsiusVers("0", "K");   // 273.15 (string acceptée)
celsiusVers("abc", "F"); // Error: "abc" n'est pas une température valide
Exercice 3 — Extension projet Générateur de profil complet

Finalise la carte de profil du cours. Crée une fonction genererProfil(donnees) qui : (1) valide que nom est une string non vide, (2) convertit age en nombre entier, (3) utilise ?? pour les champs optionnels (ville, bio), (4) retourne un objet avec initiales calculées depuis le nom.

solution — profil complet
const VILLE_DEFAUT = "Non renseignée";
const BIO_DEFAUT   = "Aucune bio";

function genererInitiales(nom) {
  return nom
    .split(" ")
    .filter(Boolean)
    .map(mot => mot[0].toUpperCase())
    .join("");
}

function genererProfil(donnees = {}) {
  // 1. Valider le nom
  const nomStr = String(donnees.nom ?? "").trim();
  if (!nomStr) throw new Error("Le nom est obligatoire");

  // 2. Convertir l'âge
  const ageNum = Math.floor(Number(donnees.age));
  if (Number.isNaN(ageNum) || ageNum < 0) {
    throw new Error("L'âge doit être un nombre positif");
  }

  // 3. Champs optionnels avec ??
  return {
    nom       : nomStr,
    initiales : genererInitiales(nomStr),  // "Alice Martin" → "AM"
    age       : ageNum,
    ville     : String(donnees.ville ?? VILLE_DEFAUT).trim(),
    bio       : String(donnees.bio   ?? BIO_DEFAUT).trim(),
    creeLe    : new Date().toLocaleDateString("fr-FR"),
  };
}

const alice = genererProfil({
  nom: "Alice Martin", age: "28", ville: "Paris"
});
// { nom: "Alice Martin", initiales: "AM", age: 28, ville: "Paris", ... }

Référence rapide

Critèrevarletconst
PortéeFonctionBlocBloc
HoistingOui (undefined)Oui (TDZ)Oui (TDZ)
Re-déclaration✅ Oui🚫 Non🚫 Non
Réassignation✅ Oui✅ Oui🚫 Non
Init. obligatoireNonNonOui
Usage🚫 JamaisValeurs mutablesPar défaut
🏆
La règle des 3 const 1. Utilise const par défaut. 2. Passe à let uniquement si tu réassignes. 3. N'utilise jamais var. Ces 3 règles éliminent 90% des bugs liés aux variables.
JavaScript · Module 1 · learnJS.eu

JavaScript
Variables

From var to const — scope, hoisting, TDZ, types, coercion. The foundations everyone uses but few truly understand.

10
Chapters
40+
Examples
4
Project steps
🏗️ What you'll build A profile card generator

You'll build a dynamic profile card that displays user data in real time — name, age, city — while visualizing the JS type of each value. This is the red-thread project of this course.

1Declare profile variables (const / let)
2Detect & display types with typeof
3Convert & validate data (coercion)
4Final version — patterns & pro naming
profile.js Live
Project:
① Declarations ② Types ③ Coercion ④ Patterns
01

Introduction to variables

A variable is a labeled box that stores a value in memory. In JavaScript, three keywords create them: var, let, and const — each with distinct rules.

🎭
The locker analogy Think of a school hallway with lockers. const is a padlocked locker — you can put stuff inside, but you can't replace the locker itself. let is a regular locker — empty and refill as you please. var is a locker with no door in a shared hallway — anyone can touch it, and you never know who changed your stuff.

var

Legacy ES1 (1995). Function scope, weird hoisting, silent re-declaration. Never use in modern code.

let

ES6. Block scope, TDZ, no re-declaration. Default choice for values that change.

const

ES6. Block scope, immutable binding (not value!). Use by default — no reassignment.

javascript
const name  = "Alice";  // ← default: const
let   score = 0;       // ← when value will change
var   age   = 30;      // ← avoid

const x; // SyntaxError: Missing initializer
let y;   // y === undefined
02

var, let, const — the differences

var — why it's problematic

var ignores {} blocks and leaks into the parent scope. It also allows silent re-declaration — a bug that can go undetected for hours.

javascript — var problems
// ① Function scope (not block)
if (true) {
  var x = 10; // var ignores the block
}
console.log(x); // 10 ← visible here!

// ② Silent re-declaration
var user = "Alice";
var user = "Bob";   // no error! Alice silently overwritten

// ③ Classic loop bug
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3  (not 0, 1, 2!)
📍 Project · Step 1 / 4Declare profile variables

We declare profile data by consciously choosing const or let based on whether the value can change.

profile.js — step 1
// Fixed data → const
const NAME       = "Alice";
const BIRTH_DATE = "1996-04-12";

// Mutable data → let
let score     = 0;
let isLoggedIn = false;
let city      = "London";

// Full profile object → const (mutation allowed)
const profile = { name: NAME, age: 28, city };
✦ CheckpointCheck your understanding

What happens if you try to add a property to an object declared with const?

03

Data types

JavaScript is a dynamically typed language — the same variable can hold different types over time. There are 7 primitive types and 1 object type.

javascript — 7 primitives
const str  = "Hello";          // string
const num  = 42;                // number
const big  = 9007199254740993n;  // bigint
const bool = true;               // boolean
const nil  = null;               // null (intentional absence)
let   u;                         // undefined (not initialized)
const id   = Symbol("id");       // symbol (unique)

typeof null  // "object" ← historic JS bug!
typeof []    // "object" ← arrays are objects
Array.isArray([]) // true ✓ reliable array check
📍 Project · Step 2 / 4Display the type of each field

We enrich the profile card to show the JS type of each value — like a live data inspector.

profile.js — step 2
function displayProfile(profile) {
  return Object.entries(profile).map(([key, value]) => `
    <div class="field">
      <span class="key">${key}</span>
      <span class="value">${value}</span>
      <span class="type">typeof: ${typeof value}</span>
    </div>
  `).join('');
}
✦ CheckpointCheck your understanding

What does typeof null return in JavaScript?

04

Scope

Scope defines where a variable is accessible. JavaScript has 4 levels: global, module, function, and block.

🎭
The Russian dolls analogy Scopes are like nested Russian dolls. From inside, you can see everything outside. But from outside, you can't see what's inside. A variable declared in a function is invisible to the global scope — but it can access global variables.
javascript — scope levels
const GLOBAL = "visible everywhere";

function outer() {
  const local = "inside outer()";

  function inner() {
    console.log(GLOBAL); // ✓ accessible
    console.log(local);  // ✓ accessible (closure)

    if (true) {
      let block = "only here";
    }
    // console.log(block); // ReferenceError
  }
}
05

Hoisting & Temporal Dead Zone

Hoisting is the mechanism by which JS conceptually moves declarations to the top of their scope before execution. Behavior differs significantly between var, let/const, and functions.

javascript — hoisting
// var: hoisted AND initialized to undefined
console.log(x); // undefined (no error!)
var x = 5;

// functions: fully hoisted
sayHi(); // ✓ works before the declaration
function sayHi() { console.log("Hi!"); }

// let / const: hoisted but NOT initialized → TDZ
console.log(name); // ReferenceError: Cannot access before init
let name = "Alice"; // ← end of TDZ
KeywordHoisted?Initialized?Before declaration?
var✅ Yes✅ undefined⚠️ Yes (undefined)
let✅ Yes❌ No🚫 No (TDZ)
const✅ Yes❌ No🚫 No (TDZ)
function✅ Yes✅ Complete✅ Yes
06

Type coercion

JavaScript automatically converts types during certain operations — this is implicit coercion. It's the source of many surprising behaviors.

javascript — implicit coercion
"3" + 4      // "34" (concatenation)
"5" - 2      // 3   (numeric coercion)
true + 1    // 2   (true → 1)
null + 1    // 1   (null → 0)
[] == false // true  😱
[] == ![]   // true  😱😱

// Always use === to avoid coercion
"1" === 1   // false ✓
📍 Project · Step 3 / 4Validate and convert profile data

We add a validation function that ensures age is a number and name is not empty — using explicit conversions.

profile.js — step 3
function validateProfile({ name, age, city }) {
  const errors = [];
  const nameStr = String(name ?? "").trim();
  if (!nameStr) errors.push("Name is required");
  const ageNum = Number(age);
  if (Number.isNaN(ageNum) || ageNum < 0) errors.push("Invalid age");
  if (errors.length) throw new Error(errors.join(", "));
  return { name: nameStr, age: Math.floor(ageNum), city: String(city ?? "Unknown") };
}
07

Nullish & Falsy values

Some values are considered false in a boolean context. Knowing the difference between falsy and nullish prevents many subtle bugs.

8 Falsy values

false · 0 · -0 · 0n · "" · null · undefined · NaN

Everything else is Truthy

"0" · "false" · [] · {} · -1 · Infinity · any function

javascript — || vs ??
// || fallback — beware of falsy!
const name = "" || "Anonymous"; // "Anonymous" (empty string is falsy)
const port = 0 || 3000;        // 3000 — bug! 0 is falsy

// ?? nullish coalescing — only null/undefined
const name2 = "" ?? "Anonymous"; // "" ← correct!
const port2 = 0 ?? 3000;        // 0  ← correct!
📍 Project · Step 4 / 4Final profile — naming & production patterns

We assemble the final version of the profile with all naming conventions and ?? for optional values.

profile.js — step 4 (final)
const MAX_AGE       = 150;
const DEFAULT_NAME  = "Anonymous";

const isLoggedIn  = true;   // boolean → "is" prefix
const hasAvatar   = false;  // boolean → "has" prefix

function createProfile(data = {}) {
  const nameStr = String(data.name ?? "").trim() || DEFAULT_NAME;
  const ageNum  = Math.min(Number(data.age) || 0, MAX_AGE);
  return {
    name     : nameStr,
    initials : nameStr.split(" ").map(w => w[0]).join("").toUpperCase(),
    age      : Math.floor(ageNum),
    city     : String(data.city ?? "Unknown"),
    createdAt: new Date().toLocaleDateString("en-GB"),
  };
}
08

Classic pitfalls

🪤 Pitfall 1 — var in loops & setTimeout

With var, all loop iterations share the same i variable. By the time the callback runs, the loop is done and i holds its final value.

javascript
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); }
// 3, 3, 3  ← var i is shared

for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); }
// 0, 1, 2  ✓
🪤 Pitfall 2 — 0.1 + 0.2 ≠ 0.3

JS uses IEEE 754 for floats. Some decimal values can't be represented exactly in binary.

javascript
0.1 + 0.2 === 0.3  // false! (0.30000000000000004)
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON  // true ✓
🪤 Pitfall 3 — NaN is not equal to NaN

NaN is the only JS value not equal to itself. Never test x === NaN — use Number.isNaN().

javascript
const x = Number("abc"); // NaN
x === NaN             // false! always
Number.isNaN(x)       // true ✓
09

Exercises

Exercise 1Inspect types of an array of values

Write an inspect(values) function that takes an array and returns an array of { value, type } objects. For null, return "null" (not "object"). For arrays, return "array".

solution
function inspect(values) {
  return values.map(v => {
    let type;
    if (v === null)           type = "null";
    else if (Array.isArray(v)) type = "array";
    else                       type = typeof v;
    return { value: v, type };
  });
}
Exercise 2Temperature converter

Create a celsiusTo(value, unit) function that converts to "F" or "K", and a normalize(value) that accepts a string or number and throws if the value is NaN after conversion.

solution
function normalize(value) {
  const n = Number(value);
  if (Number.isNaN(n)) throw new Error(`"${value}" is not a valid temperature`);
  return n;
}

function celsiusTo(value, unit) {
  const c = normalize(value);
  const conversions = { F: c => c * 9/5 + 32, K: c => c + 273.15 };
  const fn = conversions[unit.toUpperCase()];
  if (!fn) throw new Error(`Unknown unit: ${unit}`);
  return Math.round(fn(c) * 100) / 100;
}

celsiusTo(100, "F")  // 212
celsiusTo("0", "K")  // 273.15
Exercise 3 — Project extensionComplete profile generator

Finalize the profile card. Create generateProfile(data) that: (1) validates name is a non-empty string, (2) converts age to integer, (3) uses ?? for optional fields, (4) returns an object with initials computed from the name.

solution
function generateProfile(data = {}) {
  const nameStr = String(data.name ?? "").trim();
  if (!nameStr) throw new Error("Name is required");

  const ageNum = Math.floor(Number(data.age));
  if (Number.isNaN(ageNum) || ageNum < 0) throw new Error("Invalid age");

  return {
    name     : nameStr,
    initials : nameStr.split(" ").filter(Boolean).map(w => w[0].toUpperCase()).join(""),
    age      : ageNum,
    city     : String(data.city  ?? "Unknown"),
    bio      : String(data.bio   ?? "No bio"),
    createdAt: new Date().toLocaleDateString("en-GB"),
  };
}

generateProfile({ name: "Alice Martin", age: "28", city: "London" });
// { name: "Alice Martin", initials: "AM", age: 28, ... }

Quick Reference

Criterionvarletconst
ScopeFunctionBlockBlock
HoistingYes (undefined)Yes (TDZ)Yes (TDZ)
Re-declaration✅ Yes🚫 No🚫 No
Reassignment✅ Yes✅ Yes🚫 No
Init requiredNoNoYes
Usage🚫 NeverMutable valuesDefault
🏆
The 3 const rules 1. Use const by default. 2. Switch to let only when reassigning. 3. Never use var. These 3 rules eliminate 90% of variable-related bugs.