Apprenez en lisant dans l'ordre

localStorage — enregistrer des données dans le navigateur

Enregistrer des valeurs dans le navigateur avec setItem, getItem et removeItem, stocker les tableaux en JSON, et gérer null et la limite de taille.

Si tu gardes les articles du panier ou les réglages d'affichage d'un utilisateur dans des variables, ils disparaissent quand la page est rechargée ou fermée, et tout revient à l'état initial à la prochaine ouverture. Jusqu'ici, le code n'avait aucun moyen de conserver des valeurs d'un chargement de page à l'autre.

Cet article présente localStorage, qui enregistre des valeurs dans le navigateur, et les chaînes JSON qui servent à enregistrer des tableaux et des objets.

Garder des valeurs après la fermeture de la page — setItem et getItem

Un site d'actualités veut retenir la taille de police choisie par l'utilisateur pour la réutiliser à sa prochaine visite. Même si tu la mets dans une variable, comme const fontSize = "large";, les variables disparaissent quand la page est rechargée ou fermée : la page ouverte la fois suivante ne peut donc pas la lire.

localStorage (un espace de stockage clé-valeur fourni par le navigateur) enregistre une valeur avec setItem(clé, valeur), la relit avec getItem(clé) et la supprime avec removeItem(clé).

Chaque origine (la partie de l'URL qui précède le chemin : https://news.example dans https://news.example/settings) a son propre stockage : les pages d'une même origine lisent et écrivent les mêmes valeurs.

// Si une valeur d'une exécution précédente est encore enregistrée, on la supprime pour repartir de zéro
localStorage.removeItem("fontSize");

// Enregistrer quand l'utilisateur passe la taille de police à large
localStorage.setItem("fontSize", "large");

// À l'ouverture de la page, lire la valeur enregistrée et l'utiliser
const fontSize = localStorage.getItem("fontSize");
console.log(fontSize);                            // large

// Enregistrer sous la même clé remplace la valeur précédente
localStorage.setItem("fontSize", "small");
console.log(localStorage.getItem("fontSize"));    // small
Un stockage séparé pour chaque origine
Origine https://news.example
  • Les pages et onglets de cette origine partagent un seul localStorage
localStorage — clés et valeurs
  • "fontSize""small" (remplacée en enregistrant sous la même clé)
  • Reste après la fermeture ou le rechargement de la page
La page ouverte
  • Les variables comme const fontSize
  • Les variables sont recréées au rechargement
Origine https://shop.example
  • A son propre localStorage
  • Ne peut pas lire le "fontSize" enregistré par news.example
Les variables sont recréées au rechargement, mais les valeurs de localStorage restent. Les pages d'une autre origine ne peuvent pas les lire, même avec la même clé.

Si tu lis avec getItem à l'ouverture de la page et que tu écris avec setItem quand l'utilisateur change le réglage, la page garde la même taille de police même après un rechargement. Les valeurs enregistrées restent jusqu'à ce que le code de la page les supprime ou que l'utilisateur les efface dans les paramètres du navigateur.

Les valeurs enregistrées persistent d'une exécution à l'autre

La console des exercices exécute tous les exercices JavaScript du site sur la même origine : ils partagent donc un seul localStorage, dont le contenu reste même si tu rouvres la page. Pour que des valeurs enregistrées lors d'exécutions précédentes ne se mêlent pas à tes résultats, les exercices commencent par supprimer avec removeItem les clés qu'ils utilisent.

Dans une application de cartographie, enregistre le type de carte et l'unité de distance sous deux clés différentes. L'unité choisie lors de la visite précédente est déjà enregistrée, et le type choisi, selectedMapType, est déjà déclaré.

① Enregistre le type de carte choisi.

② Lis l'unité de distance enregistrée et range-la dans une variable.

③ Remplace seulement l'unité de distance par « mile ».

④ Affiche la variable de ② et les valeurs des deux clés sous la forme « km / mile / satellite ».

(Si tout s'exécute correctement, une explication apparaîtra.)

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Enregistrer des tableaux et des objets — JSON.stringify

Un site de réservation de voyages garde dans un tableau les hôtels qu'un utilisateur a mis en favoris, et veut les réafficher à sa prochaine visite. Passer le tableau tel quel à setItem ne provoque pas d'erreur, mais la valeur relue ne contient ni les identifiants des hôtels ni leurs noms.

setItem convertit en chaîne la valeur que tu lui passes avant de l'enregistrer. Transforme les tableaux et les objets en chaînes JSON avec JSON.stringify avant de les enregistrer, et reconvertis la chaîne relue en tableau ou en objet avec JSON.parse.

// Si une valeur d'une exécution précédente est encore enregistrée, on commence par la supprimer
localStorage.removeItem("favoriteHotels");
const favoriteHotels = [{ id: "H-101", name: "Hôtel du Port" }, { id: "H-204", name: "Hôtel de la Gare" }];

// Passé tel quel, le tableau est converti en chaîne au moment de l'enregistrement
localStorage.setItem("favoriteHotels", favoriteHotels);
console.log(localStorage.getItem("favoriteHotels"));   // [object Object],[object Object]

// Le convertir en chaîne JSON avant de l'enregistrer
localStorage.setItem("favoriteHotels", JSON.stringify(favoriteHotels));
const saved = localStorage.getItem("favoriteHotels");
console.log(saved);                                    // [{"id":"H-101","name":"Hôtel du Port"},{"id":"H-204","name":"Hôtel de la Gare"}]

// Reconvertir la chaîne relue en tableau avant de l'utiliser
const restored = JSON.parse(saved);
console.log(restored[1].name);                         // Hôtel de la Gare
Le contenu se perd au moment de l'enregistrement
Tableau passé telquel à setItem[object Object],…est stockégetItem renvoiela même chaîneLes noms deshôtels sont perdusJSON.stringify,puis setItem[{"id":"H-101",…est stockégetItem, puisJSON.parserestored[1].name= Hôtel de la Gare
Un tableau passé tel quel devient la chaîne [object Object] à l'enregistrement. Après getItem, impossible de retrouver le tableau d'origine.

Les nombres et les booléens sont eux aussi convertis en chaînes, sans le moindre message d'erreur : tu ne t'en rends compte que lorsqu'un calcul ou un test if sur la valeur relue donne un mauvais résultat. Le tableau ci-dessous indique, pour chaque valeur passée à setItem, la chaîne enregistrée, ce qui se passe à la relecture et la façon d'y remédier.

Valeur passée à setItemChaîne stockéeÀ la relecture (solution)
Le nombre 20"20""20" + 1 donne "201" (Number)
Le booléen false"false"Vrai dans un if (JSON.parse)
Tableau ["A-1", "B-2"]"A-1,B-2"Une seule chaîne, plus un tableau

Dans une application de vocabulaire, ajoute un mot à la liste des mots à réviser. La liste est déjà enregistrée sous la clé reviewWords, et le mot à ajouter, newWord, est déjà déclaré.

① Lis la liste enregistrée et affiche le type de la valeur.

② Reconvertis la valeur lue en tableau et ajoute newWord à la fin.

③ Enregistre de nouveau le tableau mis à jour sous la même clé.

④ Relis-le, reconvertis-le en tableau, puis affiche le nombre de mots et la propriété word du dernier.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Lire une clé non enregistrée — le null de getItem

Sur un site de recettes, les identifiants des recettes consultées sont enregistrés comme historique, et la page en affiche le nombre à l'ouverture. Un nouveau visiteur, ou quelqu'un qui a effacé son historique, n'a rien d'enregistré : si tu lis le length du résultat de JSON.parse en supposant qu'un historique existe, cette ligne lève une TypeError.

Quand tu passes à getItem une clé non enregistrée, il renvoie null, et non undefined. Si tu écris JSON.parse(localStorage.getItem(clé)) ?? [], un tableau vide n'est utilisé que lorsque rien n'est enregistré.

// Lire l'historique de consultation. Si rien n'est enregistré, utiliser un tableau vide
function loadHistory() {
  return JSON.parse(localStorage.getItem("viewHistory")) ?? [];
}

// Première visite — rien n'est enregistré, donc getItem renvoie null
localStorage.removeItem("viewHistory");
console.log(localStorage.getItem("viewHistory"));   // null
console.log(loadHistory().length);                  // 0

// Avec un historique enregistré — la chaîne redevient un tableau
localStorage.setItem("viewHistory", JSON.stringify(["R-12", "R-30"]));
console.log(loadHistory().length);                  // 2

// Après removeItem — de nouveau null
localStorage.removeItem("viewHistory");
console.log(loadHistory().length);                  // 0
Sans valeur enregistrée, JSON.parse renvoie aussi null
Rien d'enregistré(1re visite ousuppression)getItemrenvoie nullJSON.parse(null)renvoie aussi null?? donne [] :length vaut 0Enregistré["R-12","R-30"]getItem renvoieune chaîne JSONJSON.parse donneun tableau de 2?? inutilisé :length vaut 2
JSON.parse ne traite pas le null renvoyé par getItem comme une erreur : il renvoie simplement null. Avec ?? [], tu peux le traiter comme un tableau.

Si tu regroupes la lecture dans une fonction, le code appelant peut traiter le résultat comme un tableau sans vérifier null, même après un removeItem à la déconnexion. removeItem ne lève pas d'erreur même pour une clé non enregistrée : inutile, donc, de vérifier qu'il y a quelque chose avant de supprimer.

Dans une application de comptes rendus quotidiens, garde les comptes rendus inachevés comme brouillons, par date. Seul le brouillon du 10 est enregistré, et une fonction loadDraft vide est déjà déclarée.

① Fais en sorte que loadDraft renvoie le brouillon d'une date, ou une chaîne vide s'il n'y en a pas.

② Lis la clé du 11 avec getItem et affiche la valeur renvoyée.

③ Avec loadDraft, affiche le nombre de caractères des brouillons du 10 et du 11.

④ Supprime le brouillon du 10 et affiche son nombre de caractères avec loadDraft.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Retrouver le panier après un rechargement — fonctions d'enregistrement et de chargement

Dans une boutique en ligne, le panier doit garder son contenu quand la page est rechargée après l'ajout d'un article. Ajouter un article avec cart.push ne modifie que la variable : s'il manque la ligne d'enregistrement à un seul endroit, cet article disparaît au rechargement.

Regroupe l'enregistrement dans une fonction saveCart et le chargement dans une fonction loadCart, puis appelle la première juste après chaque modification du panier, et la seconde à l'ouverture de la page. setItem est synchrone (il termine l'enregistrement avant de passer à la ligne suivante) : contrairement à fetch, tu peux lire la valeur enregistrée dès la ligne suivante, sans await.

localStorage.removeItem("cart");   // Si un panier d'une exécution précédente est encore enregistré, on le supprime
// Enregistrer le panier sous forme de chaîne JSON. Pas besoin d'await
function saveCart(cart) {
  localStorage.setItem("cart", JSON.stringify(cart));
}
// Reconvertir le panier enregistré en tableau. Si rien n'est enregistré, renvoyer un panier vide
function loadCart() {
  return JSON.parse(localStorage.getItem("cart")) ?? [];
}

// Charger à l'ouverture de la page, et enregistrer après l'ajout d'un article
const cart = loadCart();
cart.push({ id: "K-310", qty: 1 });
saveCart(cart);

// Au lieu de recharger, on relit la chaîne enregistrée sans utiliser la variable cart
const reloaded = loadCart();
console.log(reloaded.length, reloaded[0].id);     // 1 K-310
Sans saveCart, rien ne revient
Ajouter K-310avec pushAppelersaveCart(cart)Recharger, puisloadCart()reloaded.lengthvaut 1Ajouter K-310avec pushOubliersaveCartRecharger, puisloadCart()reloaded.lengthvaut 0
Sans appel à saveCart, le K-310 ajouté avec push n'existe que dans la variable cart. Après un rechargement, c'est le dernier contenu enregistré qui revient.

Si tu regroupes tout ce qui modifie le panier dans une fonction qui appelle saveCart à la fin, tu n'as plus à écrire l'enregistrement à chaque appel, et tu ne peux plus l'oublier. Le chargement est lui aussi regroupé dans loadCart : il n'y a donc aucun endroit où oublier ?? [].

setItem lève une exception quand le stockage est plein

Dans Chrome, une origine peut stocker environ 5,24 millions de caractères, clés et valeurs comprises. Au-delà, setItem lève une QuotaExceededError et la valeur n'est pas enregistrée. Les lectures et écritures sont synchrones : la page cesse aussi de répondre pendant l'enregistrement d'une grosse valeur. Réserve localStorage aux petites valeurs, comme un panier ou des réglages.

Sur l'écran de contrôle qualité d'une ligne de production, compte les défauts trouvés pour chaque référence de pièce. saveDefects, loadDefects et une fonction addDefect vide sont déjà déclarés.

① Dans addDefect, lis les entrées enregistrées et cherche celle qui a la même référence.

② Si elle existe, augmente son nombre de 1 ; sinon, ajoute une entrée avec un nombre de 1. Enregistre ensuite le tableau.

③ Signale un défaut pour W-12, W-40 puis W-12, dans cet ordre.

④ Relis les entrées avec loadDefects et affiche chaque référence sous la forme « W-40 : 1 ».

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Que se passe-t-il si tu exécutes localStorage.getItem("theme") avec une clé qui n'a jamais été enregistrée ?

Question 2Après localStorage.setItem("darkMode", false), que se passe-t-il si tu utilises la valeur relue comme condition d'un if ?

Question 3Si tu recharges après cart.push(book) sans appeler saveCart, que renvoie loadCart() ?