Avez-vous déjà eu l'impression que votre code avait son propre esprit : il devenait désordonné et refusait de rester organisé ? Ne vous inquiétez pas, nous sommes tous passés par là. JavaScript peut être délicat, même pour les assistants chevronnés. Et si je vous disais qu’il existe une arme secrète pour garder les choses sous contrôle ? Entrez les fermetures.
Considérez une fermeture comme un sac à dos magique que votre fonction transporte, stockant les variables et les mémoires dont elle pourrait avoir besoin plus tard. Ces petits éléments de magie de programmation permettent d'organiser votre code, de gérer l'état sans encombrement et d'ouvrir la porte à des modèles dynamiques et flexibles.
En maîtrisant les fermetures, vous débloquerez un nouveau niveau de puissance et d’élégance dans votre code. Alors, prenez votre baguette de codage (ou un café fort ☕) et aventurons-nous ensemble dans ces royaumes cachés. ?✨
Une fermeture est simplement une fonction qui mémorise les variables de son environnement d'origine, même après que cet environnement a cessé d'exister. Au lieu de supprimer ces variables, JavaScript les range, prêtes à être invoquées en cas de besoin.
const createCounter = () => { let count = 0; // Private variable in the closure's secret realm return () => { count++; // Whispers an increment to the hidden counter return count; // Reveal the mystical number }; } // Summoning our magical counter const counter = createCounter(); console.log(counter()); // Outputs: 1 console.log(counter()); // Outputs: 2 console.log(counter()); // Outputs: 3 console.log(counter.count); // Outputs: undefined (`count` is hidden!) ?️♀️
La fonction interne conserve l'accès à count, même si createCounter a fini de s'exécuter. Cette « mémoire » est l'essence même d'une fermeture : elle assure la sécurité de vos données et permet un code puissant et flexible. ?✨
Bien que les fermetures puissent sembler magiques, elles sont simplement le résultat de la façon dont JavaScript gère la portée et la mémoire. Chaque fonction porte un lien vers son environnement lexical—le contexte dans lequel elle a été définie.
? Un environnement lexical est un enregistrement structuré de liaisons de variables, définissant ce qui est accessible dans cette portée. C'est comme une carte montrant quelles variables et fonctions vivent à l'intérieur d'un bloc ou d'une fonction donnée.
Les fermetures ne verrouillent pas une seule valeur ; ils suivent les changements au fil du temps. Si la variable de la portée externe est mise à jour, la fermeture voit la nouvelle valeur.
const createCounter = () => { let count = 0; // Private variable in the closure's secret realm return () => { count++; // Whispers an increment to the hidden counter return count; // Reveal the mystical number }; } // Summoning our magical counter const counter = createCounter(); console.log(counter()); // Outputs: 1 console.log(counter()); // Outputs: 2 console.log(counter()); // Outputs: 3 console.log(counter.count); // Outputs: undefined (`count` is hidden!) ?️♀️
Les fermetures permettent l'encapsulation en créant des variables privées avec un accès contrôlé, gèrent l'état sur plusieurs appels sans compter sur des variables globales et alimentent des comportements dynamiques comme les usines, les rappels , et des crochets.
Les frameworks comme React exploitent ces pouvoirs, permettant aux composants fonctionnels de rester sans état tout en gérant l'état avec des hooks comme useState, tout cela grâce à la magie des fermetures.
Les fermetures peuvent stocker l'état, ce qui les rend idéales pour des sorts tels que la mise en cache des résultats d'opérations coûteuses. Explorons cela étape par étape et améliorons notre sort au fur et à mesure.
Notre premier sort est simple mais puissant : un gardien de mémoire. Si on lui demande à nouveau les mêmes entrées, il renvoie instantanément le résultat mis en cache.
// A variable in the global magical realm let multiplier = 2; const createMultiplier = () => { // The inner function 'captures' the essence of the outer realm return (value: number): number => value * multiplier; }; // Our magical transformation function const double = createMultiplier(); console.log(double(5)); // Outputs: 10 multiplier = 3; console.log(double(5)); // Outputs: 15 (The magic adapts!) ✨
Certains sorts, cependant, sont trop puissants pour durer éternellement. Améliorons notre cache avec la possibilité d'oublier les vieux souvenirs. Nous allons créer un CacheEntry pour stocker non seulement les valeurs, mais leur durée de vie magique.
(Remarquez comment nous nous appuyons sur l'idée précédente : les fermetures permettent d'ajouter facilement de la complexité sans perdre le fil.)
function withCache(fn: (...args: any[]) => any) { const cache: Record<string, any> = {}; return (...args: any[]) => { const key = JSON.stringify(args); // Have we encountered these arguments before? if (key in cache) return cache[key]; // Recall of past magic! ? // First encounter? Let's forge a new memory const result = fn(...args); cache[key] = result; return result; }; } // Example usage const expensiveCalculation = (x: number, y: number) => { console.log('Performing complex calculation'); return x * y; }; // Summoning our magical cached calculation const cachedCalculation = withCache(expensiveCalculation); console.log(cachedCalculation(4, 5)); // Calculates and stores the spell console.log(cachedCalculation(4, 5)); // Uses cached spell instantly
Parfois, les sorts nécessitent du temps, comme attendre qu'un oracle (ou une API) distant réponde. Notre sort peut également gérer cela. Il attendra la promesse, stockera la valeur résolue et la renverra dans le futur – pas de récupérations répétées.
type CacheEntry<T> = { value: T; expiry: number; }; function withCache<T extends (...args: any[]) => any>( fn: T, expirationMs: number = 5 * 60 * 1000, // Default 5 minutes ) { const cache = new Map<string, CacheEntry<ReturnType<T>>>(); return (...args: Parameters<T>): ReturnType<T> => { const key = JSON.stringify(args); const now = Date.now(); // Current magical moment const cached = cache.get(key); // Is our magical memory still vibrant? if (cached && now < cached.expiry) return cached.value; // The memory has faded; it’s time to create new ones! const result = fn(...args); cache.set(key, { value: result, expiry: now + expirationMs }); return result; }; } // ... const timeLimitedCalc = withCache(expensiveCalculation, 3000); // 3-second cache console.log(timeLimitedCalc(4, 5)); // Stores result with expiration console.log(timeLimitedCalc(4, 5)); // Returns cached value before expiry setTimeout(() => { console.log(timeLimitedCalc(4, 5)); // Recalculates after expiration }, 3000);
Explorez le sort complet ici.
Notre sort de mise en cache est puissant, mais ce n'est que le début. Pensez-vous pouvoir améliorer le code ? Pensez à ajouter une gestion des erreurs, à mettre en œuvre un nettoyage magique de la mémoire ou à créer des stratégies de mise en cache plus sophistiquées. Le véritable art du codage réside dans l’expérimentation, en repoussant les limites et en réinventant les possibilités ! ??
Les fermetures sont puissantes, mais même les meilleures périodes comportent des risques. Découvrons quelques pièges courants et leurs solutions pour vous aider à gérer les fermetures en toute confiance.
Un piège JavaScript classique, souvent présenté dans les entretiens de codage, implique les boucles, en particulier la façon dont elles gèrent les variables et les fermetures de boucle.
// ... // The memory has faded; it’s time to create new ones! const result = fn(...args); if (result instanceof Promise) { return result.then((value) => { cache.set(key, { value, expiry: now + expirationMs }); return value; }); } // ...
L'exemple ci-dessus enregistre le nombre 5 cinq fois car var crée une variable unique partagée pour toutes les fermetures.
Solution 1 : utilisez let pour garantir la portée du bloc.
Le mot-clé let crée une nouvelle variable de portée bloc pour chaque itération, afin que les fermetures capturent la valeur correcte.
const createCounter = () => { let count = 0; // Private variable in the closure's secret realm return () => { count++; // Whispers an increment to the hidden counter return count; // Reveal the mystical number }; } // Summoning our magical counter const counter = createCounter(); console.log(counter()); // Outputs: 1 console.log(counter()); // Outputs: 2 console.log(counter()); // Outputs: 3 console.log(counter.count); // Outputs: undefined (`count` is hidden!) ?️♀️
Solution 2 : utilisez une IIFE (expression de fonction immédiatement invoquée).
Un IIFE crée une nouvelle portée pour chaque itération, garantissant une gestion appropriée des variables dans la boucle.
// A variable in the global magical realm let multiplier = 2; const createMultiplier = () => { // The inner function 'captures' the essence of the outer realm return (value: number): number => value * multiplier; }; // Our magical transformation function const double = createMultiplier(); console.log(double(5)); // Outputs: 10 multiplier = 3; console.log(double(5)); // Outputs: 15 (The magic adapts!) ✨
Astuce bonus : ? L'astuce fonctionnelle.
Peu de sorciers connaissent ce sort, et pour être honnête, je l’ai rarement (voire jamais) vu mentionné lors des entretiens de codage. Saviez-vous que setTimeout peut transmettre des arguments supplémentaires directement à son rappel ?
function withCache(fn: (...args: any[]) => any) { const cache: Record<string, any> = {}; return (...args: any[]) => { const key = JSON.stringify(args); // Have we encountered these arguments before? if (key in cache) return cache[key]; // Recall of past magic! ? // First encounter? Let's forge a new memory const result = fn(...args); cache[key] = result; return result; }; } // Example usage const expensiveCalculation = (x: number, y: number) => { console.log('Performing complex calculation'); return x * y; }; // Summoning our magical cached calculation const cachedCalculation = withCache(expensiveCalculation); console.log(cachedCalculation(4, 5)); // Calculates and stores the spell console.log(cachedCalculation(4, 5)); // Uses cached spell instantly
Les fermetures conservent une référence à leur portée externe, ce qui signifie que les variables peuvent rester plus longtemps que prévu, entraînant des fuites de mémoire.
type CacheEntry<T> = { value: T; expiry: number; }; function withCache<T extends (...args: any[]) => any>( fn: T, expirationMs: number = 5 * 60 * 1000, // Default 5 minutes ) { const cache = new Map<string, CacheEntry<ReturnType<T>>>(); return (...args: Parameters<T>): ReturnType<T> => { const key = JSON.stringify(args); const now = Date.now(); // Current magical moment const cached = cache.get(key); // Is our magical memory still vibrant? if (cached && now < cached.expiry) return cached.value; // The memory has faded; it’s time to create new ones! const result = fn(...args); cache.set(key, { value: result, expiry: now + expirationMs }); return result; }; } // ... const timeLimitedCalc = withCache(expensiveCalculation, 3000); // 3-second cache console.log(timeLimitedCalc(4, 5)); // Stores result with expiration console.log(timeLimitedCalc(4, 5)); // Returns cached value before expiry setTimeout(() => { console.log(timeLimitedCalc(4, 5)); // Recalculates after expiration }, 3000);
Que se passe-t-il ici ? La fermeture conserve l'intégralité de la variable de données, même si seule une petite partie est nécessaire, ce qui risque de gaspiller des ressources importantes.
La solution consiste à gérer soigneusement ce que les fermetures capturent et à libérer explicitement les références inutiles. Cela garantit que les grands ensembles de données sont chargés uniquement en cas de besoin et libérés de manière proactive grâce à une méthode de nettoyage.
// ... // The memory has faded; it’s time to create new ones! const result = fn(...args); if (result instanceof Promise) { return result.then((value) => { cache.set(key, { value, expiry: now + expirationMs }); return value; }); } // ...
Les fermetures peuvent entraîner un comportement inattendu lorsque l'état partagé est muté. Ce qui semble être une simple référence peut entraîner des effets secondaires involontaires.
for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // Logs 5, five times ? }, i * 1000); }
Que se passe-t-il ici ? La méthode getUsers expose le tableau des utilisateurs, rompant l'encapsulation et risquant des effets secondaires involontaires dus à des modifications externes.
La solution est de renvoyer une copie de l'état interne. Cela empêche les modifications externes, maintient l'intégrité des données et sauvegarde la logique interne de la fermeture.
for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // Works as expected ? }, i * 1000); }
La maîtrise de ces techniques vous aidera à exercer la magie des fermetures en toute confiance. La véritable maîtrise réside dans la compréhension et non dans l’évitement. ✨
Les fermetures peuvent sembler complexes au premier abord, mais elles libèrent le potentiel d'écrire du code plus élégant et plus efficace. En transformant des fonctions simples en entités persistantes et avec état, les fermetures peuvent partager avec élégance des secrets à travers le temps et l'espace. Cette fonctionnalité puissante élève JavaScript du statut de simple langage de script à celui d'un outil puissant et flexible pour résoudre des problèmes complexes.
Votre voyage ne s’arrête pas là ; approfondissez les modèles asynchrones, la programmation fonctionnelle et le fonctionnement interne des moteurs JavaScript. Chaque étape révèle davantage de couches de ce langage enchanteur, suscitant de nouvelles idées et solutions.
Après tout, la véritable maîtrise vient de la curiosité et de l’exploration. Que votre code soit toujours élégant, efficace et un peu magique. ?
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!