是否曾經感覺您的程式碼有自己的想法——變得越來越混亂並且拒絕保持井井有條?別擔心,我們都經歷過。即使對於經驗豐富的嚮導來說,JavaScript 也可能很棘手。但如果我告訴你有一個秘密武器可以控制一切呢?輸入閉包。
將閉包視為函數攜帶的神奇背包,用於儲存稍後可能需要的變數和記憶體。這些小小的程式設計魔法可以讓您的程式碼保持井井有條,管理狀態而不混亂,並為動態、靈活的模式打開大門。
透過掌握閉包,您將在代碼中解鎖新的功能和優雅水平。所以,拿起你的程式棒(或一杯濃咖啡☕),讓我們一起冒險進入這些隱藏的領域。 ?✨
閉包只是一個函數,它會記住原始環境中的變數—即使在該環境不再存在之後也是如此。 JavaScript 不會丟棄這些變量,而是將它們隱藏起來,以便在需要時呼叫。
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!) ?️♀️
即使 createCounter 已完成運行,內部函數仍保留對 count 的存取。這種「內存」是閉包的本質——保證資料安全並支援強大、靈活的程式碼。 ?✨
雖然閉包可能感覺很神奇,但它們只是 JavaScript 處理 範圍 和 記憶體 的結果。每個函數都帶有一個指向其詞法環境的連結-定義它的上下文。
? 詞法環境是變數綁定的結構化記錄,定義在該範圍內可存取的內容。它就像一張地圖,顯示哪些變數和函數位於給定的區塊或函數內。
閉包不僅僅鎖定一個值;他們追蹤隨著時間的推移而發生的變化。如果外部作用域的變數更新,閉包就會看到新值。
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!) ?️♀️
閉包透過建立具有受控存取權限的私有變數來實現封裝,跨多個呼叫管理狀態而不依賴全域變量,並支援動態行為如工廠、回調,和掛鉤。
像 React 這樣的框架利用這些能力,讓功能元件保持無狀態,同時使用 useState 等鉤子管理狀態 - 這一切都歸功於閉包的魔力。
閉包可以儲存狀態,這使得它們非常適合快取昂貴操作的結果。讓我們一步步探索這個並增強我們的咒語。
我們的第一個咒語簡單而強大:記憶守護者。如果再次請求相同的輸入,它會立即傳回快取的結果。
// 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!) ✨
然而,有些咒語太過強大,無法永遠持續。讓我們透過忘記舊記憶的能力來增強我們的緩存。我們將創建一個 CacheEntry 來不僅儲存值,還儲存它們神奇的生命週期。
(請注意我們如何在之前的想法的基礎上進行構建 - 閉包可以輕鬆地增加複雜性而不丟失軌道。)
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
有時,咒語需要時間-例如等待遙遠的神諭(或 API)回應。我們的咒語也可以解決這個問題。它將等待 Promise,儲存解析的值,並在將來返回它——不會重複獲取。
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);
在這裡探索完整的咒語。
我們的快取咒語很強大,但這只是開始。您認為您可以升級程式碼嗎?考慮添加錯誤處理、實現神奇的記憶體清理或創建更複雜的快取策略。真正的編碼藝術在於實驗、突破界限和重新想像可能性! ??
閉包非常強大,但即使是最好的咒語也伴隨著風險。讓我們揭示一些常見的陷阱及其解決方案,以幫助您自信地使用閉包。
編碼面試中經常出現的一個經典 JavaScript 陷阱涉及循環,具體來說,它們如何處理循環變數和閉包。
// ... // 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; }); } // ...
上面的範例將數字 5 記錄五次,因為 var 為所有閉包建立了一個共享變數。
解 1:使用 let 來確保區塊作用域。
let 關鍵字為每次迭代建立一個新的區塊範圍變量,因此閉包會擷取正確的值。
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!) ?️♀️
解 2:使用 IIFE(立即呼叫函數表達式)。
IIFE 為每次迭代建立一個新的作用域,確保循環內正確的變數處理。
// 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!) ✨
額外提示:?功能性技巧。
很少有巫師知道這個咒語,說實話,我很少(如果有的話)在編碼面試中看到它被提及。您知道 setTimeout 可以直接向其回呼傳遞附加參數嗎?
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
閉包維護對其外部作用域的引用,這意味著變數可能會比預期停留更長時間,從而導致記憶體洩漏。
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);
這裡發生了什麼事?即使只需要一小部分,閉包也會保留整個資料變量,這可能會浪費大量資源。
解決方案是仔細管理閉包捕獲的內容並明確釋放不必要的引用。這可確保僅在需要時載入大型資料集,並透過清理方法主動釋放。
// ... // 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; }); } // ...
當共享狀態改變時,閉包可能會導致意外行為。看似簡單的參考可能會導致意想不到的副作用。
for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // Logs 5, five times ? }, i * 1000); }
這裡發生了什麼事? getUsers 方法公開了使用者數組,破壞了封裝並冒著外部修改帶來意想不到的副作用的風險。
解決方案是傳回內部狀態的副本。這可以防止外部修改,保持資料完整性,並保護閉包的內部邏輯。
for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // Works as expected ? }, i * 1000); }
掌握這些技巧將幫助您自信地運用閉包的魔力。真正的掌握在於理解,而不是迴避。 ✨
閉包最初可能看起來很複雜,但它們釋放了編寫更優雅、更有效率的程式碼的潛力。透過將簡單的函數轉變為持久的、有狀態的實體,閉包可以優雅地跨時間和空間共享秘密。這項強大的功能將 JavaScript 從簡單的腳本語言提升為解決複雜問題的強大且靈活的工具。
你的旅程並沒有在這裡結束;更深入地了解非同步模式、函數式程式設計和 JavaScript 引擎的內部運作原理。每一步都揭示了這種迷人語言的更多層次,激發了新的想法和解決方案。
畢竟,真正的掌握來自好奇心和探索。願你的程式碼永遠優雅、高效,而且有點神奇。 ?
以上是揭秘閉包:探索 JavaScript 的隱藏領域的詳細內容。更多資訊請關注PHP中文網其他相關文章!