Javascript 4
Exercice 1 — Des valeurs par défaut
function createUser(name, role = "user") {
return { name, role };
}
console.log(createUser("Alice")); // role: "user"
console.log(createUser("Bob", "admin")); // role: "admin"
console.log(createUser("Carl", null)); // role: null ⚠️
console.log(createUser("Dora", undefined)); // role: "user"
Une valeur par défaut ne se déclenche que si l’argument vaut undefined.
null est une valeur comme une autre : elle est passée telle quelle. C’est la
même distinction qu’entre || et ??.
Exercice 2 — Naviguer dans un objet incertain
for (const user of users) {
console.log(user.name, ":", user.address?.city ?? "Ville inconnue");
}
?. court-circuite dès qu’il rencontre null ou undefined et renvoie
undefined, au lieu de lever Cannot read properties of null.
Le piège :
const settings = { volume: 0, label: "" };
console.log(settings.volume || 50); // 50 ⚠️ on a perdu le volume à zéro
console.log(settings.volume ?? 50); // 0 ✅
|| se déclenche sur toutes les valeurs falsy — 0, "", false, NaN.
?? ne se déclenche que sur null et undefined. Pour une valeur par défaut,
c’est presque toujours ?? qu’il faut.
Exercice 3 — Déstructurer plutôt que répéter
function describe({ firstname, lastname, city = "ville inconnue" }) {
return `${firstname} ${lastname}, ${city}`;
}
Bonus :
const [first, ...others] = [1, 2, 3, 4];
console.log(first, others); // 1 [2, 3, 4]
⚠️ La valeur par défaut city = "..." ne s’applique, là encore, que si la
propriété est absente ou vaut undefined.
Exercice 4 — Copier en changeant une chose
const admin = { ...user, role: "admin" };
const active = { ...user, active: true };
const numbers2 = [1, ...numbers, 4];
const merged = { ...{ a: 1 }, ...{ b: 2 } };
L’ordre décide qui gagne :
{ ...user, role: "admin" } // "admin" écrase la valeur de user
{ role: "admin", ...user } // user écrase "admin" — presque jamais ce qu'on veut
Le spread ne copie qu’un niveau. Les objets imbriqués restent partagés : c’est exactement le sujet des exercices suivants.
Exercice 5 — Accepter n’importe quel nombre d’arguments
function max(...numbers) {
return numbers.length === 0 ? 0 : Math.max(...numbers);
}
Le même ... fait deux choses opposées :
- à la déclaration (rest) : rassemble les arguments dans un tableau ;
- à l’appel (spread) : étale un tableau en arguments séparés.
Ici les deux cohabitent dans quatre lignes.
Exercice 6 — Construire une phrase
console.log(
`Bonjour ${user.firstname} ${user.lastname},
vous avez ${user.age} ans.
L'an prochain vous en aurez ${user.age + 1}.`,
);
Les retours à la ligne sont pris tels quels — plus besoin de \n. Et ${}
accepte n’importe quelle expression, ici un calcul.
Exercice 7 — Mutation cachée
.push() modifie le tableau sur place. Comme user ne contient qu’une
référence vers ce tableau, l’utilisateur d’origine est altéré — et la
fonction retourne le même objet, pas un nouveau.
function addTag(user, tag) {
return {
...user,
tags: [...user.tags, tag], // nouveau tableau, pas un push
};
}
const updated = addTag(users[0], "editor");
console.log(updated === users[0]); // false
console.log(updated.tags === users[0].tags); // false
console.log(users[0].tags); // ["admin"] — intact
Exercice 8 — Une copie qui n’en est pas une
const newState = state ne crée aucun objet : les deux noms désignent la
même adresse mémoire. newState.counter++ modifie donc l’original, et
=== compare les références — d’où true.
const interdit de réaffecter le nom, pas de modifier l’objet pointé.
function increment(state) {
return { ...state, counter: state.counter + 1 };
}
const oldState = { counter: 0 };
const newState = increment(oldState);
console.log(oldState.counter); // 0
console.log(newState.counter); // 1
console.log(oldState === newState); // false
Exercice 9 — Retirer sans splice
function removeUserById(users, id) {
return users.filter((user) => user.id !== id);
}
⚠️ La version d’origine avait un bug en plus d’être mutante : si l’id
n’existe pas, findIndex renvoie -1, et splice(-1, 1) supprime le
dernier élément. filter n’a pas ce problème.
Exercice 10 — Incrémenter sans modifier
function incrementScore(player) {
return { ...player, score: player.score + 1 };
}
Le spread recopie toutes les autres propriétés : ajouter un champ à player
plus tard ne demandera aucune modification ici.
Exercice 11 — Immutabilité profonde
function updateLastname(state, newLastname) {
return {
...state,
user: {
...state.user,
profile: { ...state.user.profile, lastname: newLastname },
},
};
}
Avec un seul niveau de spread, state.user !== next.user tombe : user reste
la même référence, et modifier son profile toucherait l’état d’origine. Il
faut cloner chaque niveau du chemin que l’on modifie — et seulement
celui-là.
Exercice 12 — Le piège de la référence partagée
settings: defaultSettings ne copie rien : chaque utilisateur reçoit la même
référence. Ils partagent donc physiquement le même objet de réglages.
function createUser(name) {
return { name, settings: { ...defaultSettings } };
}
const user1 = createUser("Alice");
const user2 = createUser("Bob");
user1.settings.theme = "light";
console.log(user2.settings.theme); // "dark"
console.log(user1.settings !== user2.settings); // true
⚠️ Ce clone reste superficiel. Si defaultSettings contenait lui-même un
objet imbriqué, il resterait partagé.
Exercice 13 — Ne recréer que ce qui change
function setUserOnline(state, userId) {
return {
...state,
users: state.users.map((user) =>
user.id === userId
? { ...user, online: true } // seul celui-là est recréé
: user, // les autres gardent leur référence
),
};
}
Rendre user tel quel dans la branche else n’est pas une paresse d’écriture :
c’est ce qui permet à React de comparer les références et de ne réafficher que
la ligne modifiée. Recréer tous les objets « pour faire propre » ferait
retomber toute la liste.