[Medium] Deep Clone
1. What is Deep Clone?
Qu'est-ce que le Deep Clone ?
Le Deep Clone (copie profonde) consiste à créer un nouvel objet et à copier récursivement toutes les propriétés de l'objet original ainsi que tous ses objets et tableaux imbriqués. L'objet issu du Deep Clone est complètement indépendant de l'original : modifier l'un n'affecte pas l'autre.
Copie superficielle vs Copie profonde
Shallow Clone (Copie superficielle) : Copie uniquement les propriétés du premier niveau de l'objet ; les objets imbriqués partagent toujours la même référence.
// Exemple de copie superficielle
const original = {
name: 'John',
address: {
city: 'Taipei',
country: 'Taiwan',
},
};
const shallowCopy = { ...original };
shallowCopy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Kaohsiung' ❌ L'objet original a aussi été modifié
Deep Clone (Copie profonde) : Copie récursivement toutes les couches de propriétés, complètement indépendant.
// Exemple de copie profonde
const original = {
name: 'John',
address: {
city: 'Taipei',
country: 'Taiwan',
},
};
const deepCopy = deepClone(original);
deepCopy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Taipei' ✅ L'objet original n'est pas affecté
2. Implementation Methods
Méthodes d'implémentation
Méthode 1 : Utilisation de JSON.parse et JSON.stringify
Avantages : Simple et rapide Inconvénients : Ne peut pas gérer les fonctions, undefined, Symbol, Date, RegExp, Map, Set et autres types spéciaux
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
// Test
const original = {
name: 'John',
age: 30,
address: {
city: 'Taipei',
country: 'Taiwan',
},
hobbies: ['reading', 'coding'],
};
const cloned = deepClone(original);
cloned.address.city = 'Kaohsiung';
cloned.hobbies.push('swimming');
console.log(original.address.city); // 'Taipei' ✅
console.log(original.hobbies); // ['reading', 'coding'] ✅
Limitations :
const obj = {
date: new Date(),
func: function () {},
undefined: undefined,
symbol: Symbol('test'),
regex: /test/g,
};
const cloned = deepClone(obj);
console.log(cloned.date); // {} ❌ Date devient un objet vide
console.log(cloned.func); // undefined ❌ La fonction est perdue
console.log(cloned.undefined); // undefined ✅ Mais JSON.stringify le supprime
console.log(cloned.symbol); // undefined ❌ Symbol est perdu
console.log(cloned.regex); // {} ❌ RegExp devient un objet vide
Méthode 2 : Implémentation récursive (gestion des types de base et objets)
function deepClone(obj) {
// Gestion de null et des types de base
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Gestion de Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Gestion de RegExp
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// Gestion des tableaux
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
// Gestion des objets
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
// Test
const original = {
name: 'John',
date: new Date(),
regex: /test/g,
hobbies: ['reading', 'coding'],
address: {
city: 'Taipei',
},
};
const cloned = deepClone(original);
cloned.date.setFullYear(2025);
cloned.hobbies.push('swimming');
console.log(original.date.getFullYear()); // 2024 ✅ Non affecté
console.log(original.hobbies); // ['reading', 'coding'] ✅
Méthode 3 : Implémentation complète (gestion de Map, Set, Symbol, etc.)
function deepClone(obj, map = new WeakMap()) {
// Gestion de null et des types de base
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Gestion des références circulaires
if (map.has(obj)) {
return map.get(obj);
}
// Gestion de Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Gestion de RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Gestion de Map
if (obj instanceof Map) {
const clonedMap = new Map();
map.set(obj, clonedMap);
obj.forEach((value, key) => {
clonedMap.set(deepClone(key, map), deepClone(value, map));
});
return clonedMap;
}
// Gestion de Set
if (obj instanceof Set) {
const clonedSet = new Set();
map.set(obj, clonedSet);
obj.forEach((value) => {
clonedSet.add(deepClone(value, map));
});
return clonedSet;
}
// Gestion des tableaux
if (Array.isArray(obj)) {
const clonedArray = [];
map.set(obj, clonedArray);
obj.forEach((item) => {
clonedArray.push(deepClone(item, map));
});
return clonedArray;
}
// Gestion des objets
const cloned = {};
map.set(obj, cloned);
// Gestion des propriétés Symbol
const symbolKeys = Object.getOwnPropertySymbols(obj);
const stringKeys = Object.keys(obj);
// Copier les propriétés normales
stringKeys.forEach((key) => {
cloned[key] = deepClone(obj[key], map);
});
// Copier les propriétés Symbol
symbolKeys.forEach((symbolKey) => {
cloned[symbolKey] = deepClone(obj[symbolKey], map);
});
return cloned;
}
// Test
const symbolKey = Symbol('test');
const original = {
name: 'John',
[symbolKey]: 'symbol value',
date: new Date(),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
hobbies: ['reading', 'coding'],
};
const cloned = deepClone(original);
console.log(cloned[symbolKey]); // 'symbol value' ✅
console.log(cloned.map.get('key')); // 'value' ✅
console.log(cloned.set.has(1)); // true ✅
Méthode 4 : Gestion des références circulaires
function deepClone(obj, map = new WeakMap()) {
// Gestion de null et des types de base
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Gestion des références circulaires
if (map.has(obj)) {
return map.get(obj);
}
// Gestion de Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Gestion de RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Gestion des tableaux
if (Array.isArray(obj)) {
const clonedArray = [];
map.set(obj, clonedArray);
obj.forEach((item) => {
clonedArray.push(deepClone(item, map));
});
return clonedArray;
}
// Gestion des objets
const cloned = {};
map.set(obj, cloned);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key], map);
}
}
return cloned;
}
// Test des références circulaires
const original = {
name: 'John',
};
original.self = original; // Référence circulaire
const cloned = deepClone(original);
console.log(cloned.self === cloned); // true ✅ Référence circulaire correctement gérée
console.log(cloned !== original); // true ✅ Objets différents
3. Common Interview Questions
Questions d'entretien fréquentes
Question 1 : Implémentation basique du Deep Clone
Implémentez une fonction deepClone capable de copier profondément des objets et des tableaux.
Cliquez pour voir la réponse
function deepClone(obj) {
// Gestion de null et des types de base
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Gestion de Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Gestion de RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Gestion des tableaux
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
// Gestion des objets
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
// Test
const original = {
name: 'John',
age: 30,
address: {
city: 'Taipei',
country: 'Taiwan',
},
hobbies: ['reading', 'coding'],
};
const cloned = deepClone(original);
cloned.address.city = 'Kaohsiung';
cloned.hobbies.push('swimming');
console.log(original.address.city); // 'Taipei' ✅
console.log(original.hobbies); // ['reading', 'coding'] ✅
Question 2 : Gestion des références circulaires
Implémentez une fonction deepClone capable de gérer les références circulaires.
Cliquez pour voir la réponse
function deepClone(obj, map = new WeakMap()) {
// Gestion de null et des types de base
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Gestion des références circulaires
if (map.has(obj)) {
return map.get(obj);
}
// Gestion de Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Gestion de RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Gestion des tableaux
if (Array.isArray(obj)) {
const clonedArray = [];
map.set(obj, clonedArray);
obj.forEach((item) => {
clonedArray.push(deepClone(item, map));
});
return clonedArray;
}
// Gestion des objets
const cloned = {};
map.set(obj, cloned);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key], map);
}
}
return cloned;
}
// Test des références circulaires
const original = {
name: 'John',
};
original.self = original; // Référence circulaire
const cloned = deepClone(original);
console.log(cloned.self === cloned); // true ✅
console.log(cloned !== original); // true ✅
Points clés :
- Utiliser
WeakMappour suivre les objets déjà traités - Avant de créer un nouvel objet, vérifier s'il existe déjà dans le map
- S'il existe, retourner directement la référence du map pour éviter la récursion infinie
Question 3 : Limitations de JSON.parse et JSON.stringify
Expliquez les limitations de l'utilisation de JSON.parse(JSON.stringify()) pour le Deep Clone et proposez des solutions.
Cliquez pour voir la réponse
Limitations :
-
Ne peut pas gérer les fonctions
const obj = { func: function () {} };const cloned = JSON.parse(JSON.stringify(obj));console.log(cloned.func); // undefined ❌ -
Ne peut pas gérer undefined
const obj = { value: undefined };const cloned = JSON.parse(JSON.stringify(obj));console.log(cloned.value); // undefined (mais la propriété est supprimée) ❌ -
Ne peut pas gérer Symbol
const obj = { [Symbol('key')]: 'value' };const cloned = JSON.parse(JSON.stringify(obj));console.log(cloned); // {} ❌ La propriété Symbol est perdue -
Date devient une chaîne de caractères
const obj = { date: new Date() };const cloned = JSON.parse(JSON.stringify(obj));console.log(cloned.date); // "2024-01-01T00:00:00.000Z" ❌ Devient une chaîne -
RegExp devient un objet vide
const obj = { regex: /test/g };const cloned = JSON.parse(JSON.stringify(obj));console.log(cloned.regex); // {} ❌ Devient un objet vide -
Ne peut pas gérer Map, Set
const obj = { map: new Map([['key', 'value']]) };const cloned = JSON.parse(JSON.stringify(obj));console.log(cloned.map); // {} ❌ Devient un objet vide -
Ne peut pas gérer les références circulaires
const obj = { name: 'John' };obj.self = obj;JSON.parse(JSON.stringify(obj)); // ❌ Erreur : Converting circular structure to JSON
Solution : Utiliser une implémentation récursive avec un traitement spécial pour les différents types.
4. Best Practices
Bonnes pratiques
Pratiques recommandées
// 1. Choisir la méthode appropriée en fonction des besoins
// Si seuls des objets basiques et des tableaux doivent être traités, utiliser une implémentation récursive simple
function simpleDeepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (Array.isArray(obj)) return obj.map((item) => simpleDeepClone(item));
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = simpleDeepClone(obj[key]);
}
}
return cloned;
}
// 2. Si des types complexes doivent être gérés, utiliser l'implémentation complète
function completeDeepClone(obj, map = new WeakMap()) {
// ... Implémentation complète
}
// 3. Utiliser WeakMap pour gérer les références circulaires
// WeakMap n'empêche pas la collecte des déchets, adapté au suivi des références d'objets
Pratiques à éviter
// 1. Ne pas abuser de JSON.parse(JSON.stringify())
// ❌ Les fonctions, Symbol, Date et autres types spéciaux sont perdus
const cloned = JSON.parse(JSON.stringify(obj));
// 2. Ne pas oublier de gérer les références circulaires
// ❌ Provoque un débordement de pile
function deepClone(obj) {
const cloned = {};
for (let key in obj) {
cloned[key] = deepClone(obj[key]); // Récursion infinie
}
return cloned;
}
// 3. Ne pas oublier de gérer Date, RegExp et autres types spéciaux
// ❌ Ces types nécessitent un traitement spécial
5. Interview Summary
Résumé pour les entretiens
Aide-mémoire
Deep Clone :
- Définition : Copier récursivement un objet et toutes ses propriétés imbriquées, complètement indépendant
- Méthodes : Implémentation récursive, JSON.parse(JSON.stringify()), structuredClone()
- Points clés : Gestion des types spéciaux, références circulaires, propriétés Symbol
Points d'implémentation :
- Gérer les types de base et null
- Gérer Date, RegExp et autres objets spéciaux
- Gérer les tableaux et les objets
- Gérer les références circulaires (avec WeakMap)
- Gérer les propriétés Symbol
Exemple de réponse en entretien
Q : Veuillez implémenter une fonction Deep Clone.
"Le Deep Clone consiste à créer un nouvel objet complètement indépendant en copiant récursivement toutes les propriétés imbriquées. Mon implémentation gère d'abord les types de base et null, puis effectue un traitement spécial pour différents types comme Date, RegExp, tableaux et objets. Pour gérer les références circulaires, j'utilise un WeakMap pour suivre les objets déjà traités. Pour les propriétés Symbol, j'utilise Object.getOwnPropertySymbols pour les obtenir et les copier. Cela garantit que l'objet copié en profondeur est complètement indépendant de l'objet original, et que modifier l'un n'affecte pas l'autre."
Q : Quelles sont les limitations de JSON.parse(JSON.stringify()) ?
"Les principales limitations de cette méthode sont : 1) Ne peut pas gérer les fonctions, qui sont supprimées ; 2) Ne peut pas gérer undefined et Symbol, ces propriétés sont ignorées ; 3) Les objets Date deviennent des chaînes de caractères ; 4) RegExp devient un objet vide ; 5) Ne peut pas gérer Map, Set et autres structures de données spéciales ; 6) Ne peut pas gérer les références circulaires, ce qui provoque une erreur. Si ces cas spéciaux doivent être gérés, il faut utiliser une implémentation récursive."