首頁 >web前端 >js教程 >揭秘閉包:探索 JavaScript 的隱藏領域

揭秘閉包:探索 JavaScript 的隱藏領域

DDD
DDD原創
2024-12-12 12:22:24285瀏覽

Closures Unveiled: Exploring the Hidden Realms of JavaScript

目錄

  • 逃離編碼混亂
  • 閉包到底是什麼?
  • 崩潰:關閉揭幕
  • 實用法術:有閉包的緩存之旅
  • 常見陷阱以及如何避免它們
  • 旅程繼續

逃離編碼混亂? ‍♂️

是否曾經感覺您的程式碼有自己的想法——變得越來越混亂並且拒絕保持井井有條?別擔心,我們都經歷過。即使對於經驗豐富的嚮導來說,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 等鉤子管理狀態 - 這一切都歸功於閉包的魔力。


實用法術:帶閉包的緩存之旅? ‍♂️

閉包可以儲存狀態,這使得它們非常適合快取昂貴操作的結果。讓我們一步步探索這個並增強我們的咒語。

步驟 1:?️ 內存守護者 – 基本緩存

我們的第一個咒語簡單而強大:記憶守護者。如果再次請求相同的輸入,它會立即傳回快取的結果。

// 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!) ✨

步驟 2:⏳ 褪色咒語 – 過期緩存

然而,有些咒語太過強大,無法永遠持續。讓我們透過忘記舊記憶的能力來增強我們的緩存。我們將創建一個 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

步驟 3: ?非同步魔法 – Promise 處理

有時,咒語需要時間-例如等待遙遠的神諭(或 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);

在這裡探索完整的咒語。

巫師的挑戰??‍♂️

我們的快取咒語很強大,但這只是開始。您認為您可以升級程式碼嗎?考慮添加錯誤處理、實現神奇的記憶體清理或創建更複雜的快取策略。真正的編碼藝術在於實驗、突破界限和重新想像可能性! ??


常見的陷阱以及如何避免它們? ️

閉包非常強大,但即使是最好的咒語也伴隨著風險。讓我們揭示一些常見的陷阱及其解決方案,以幫助您自信地使用閉包。

陷阱#1:?️ 偷偷摸摸的循環陷阱

編碼面試中經常出現的一個經典 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

陷阱#2:?記憶體洩漏—無聲的威脅

閉包維護對其外部作用域的引用,這意味著變數可能會比預期停留更長時間,從而導致記憶體洩漏。

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;
  });
}

// ...

陷阱 #3:?️ 突變混亂

當共享狀態改變時,閉包可能會導致意外行為。看似簡單的參考可能會導致意想不到的副作用。

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);
}

掌握關閉的黃金法則?

  1. 有意捕獲:了解閉包捕獲的內容以避免不必要的依賴關係和記憶體問題。
  2. 明智地作用域變數:使用區塊作用域來防止共享引用錯誤並確保正確的變數捕獲。
  3. 擁抱不變性:支援不可變模式,回傳副本而不是改變共享狀態,以避免副作用。
  4. 練習清理:釋放不需要的引用以防止記憶體洩漏,尤其是對於大型或敏感資料。

掌握這些技巧將幫助您自信地運用閉包的魔力。真正的掌握在於理解,而不是迴避。 ✨


旅程仍在繼續

閉包最初可能看起來很複雜,但它們釋放了編寫更優雅、更有效率的程式碼的潛力。透過將簡單的函數轉變為持久的、有狀態的實體,閉包可以優雅地跨時間和空間共享秘密。這項強大的功能將 JavaScript 從簡單的腳本語言提升為解決複雜問題的強大且靈活的工具。

你的旅程並沒有在這裡結束;更深入地了解非同步模式、函數式程式設計和 JavaScript 引擎的內部運作原理。每一步都揭示了這種迷人語言的更多層次,激發了新的想法和解決方案。

畢竟,真正的掌握來自好奇心和探索。願你的程式碼永遠優雅、高效,而且有點神奇。 ?

以上是揭秘閉包:探索 JavaScript 的隱藏領域的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn