iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0
Modern Web

30天一起搞懂Web觀念系列 第 16

[DAY16] 閉包是什麼?

  • 分享至 

  • xImage
  •  

這篇和前幾篇與之後比較沒有關聯,但因為突然有看到,想說寫一下

閉包(Closure)是什麼?

在程式設計中,閉包(Closure)是一個很重要的概念

簡單來說:

閉包就是「函式可以記住並存取它外層作用域的變數」,即使這個外層函式已經執行結束

因為閉包能記住外部變數,所以它常被用來做狀態保存,像是一個計數器、一個暫存快取,或者模擬私有變數


舉例來說:

func makeCounter() -> () -> Int {
    var count = 0 // count 是 makeCounter 裡的區域變數

    // 回傳一個閉包,這個閉包捕獲了外層的 count
    let counterClosure: () -> Int = {
        count += 1
        return count
    }

    return counterClosure
}

let counter1 = makeCounter()
print(counter1()) // 1
print(counter1()) // 2
print(counter1()) // 3

let counter2 = makeCounter()
print(counter2()) // 1
print(counter2()) // 2

print(counter1()) // 4 (counter1 保持自己的狀態)

這裡的 count 原本應該在 makeCounter 執行完後就消失,但因為閉包「捕獲」了它,讓 count 可以持續存在並保存狀態

Note: 這裡說的「捕獲」和之前事件的冒泡與捕獲是完全不一樣的事情喔!


閉包的應用場景

  1. 狀態管理(React 的 useState 就是閉包概念)
function useState(initialState) {
  let state = initialState;

  function getState() {
    return state;
  }

  function setState(updatedState) {
    state = updatedState;
  }

  return [getState, setState];
}

const [count, setCount] = useState(0);

console.log(count()); // 0
setCount(1);
console.log(count()); // 1
setCount(500);
console.log(count()); // 500


  1. 快取(Memoization)

閉包可以保存計算結果,避免重複運算

function memoize(fn) {
  const cache = {};
  return (...args) => {
    const key = JSON.stringify(args);
    if (key in cache) {
      return cache[key];
    } else {
      const val = fn(...args);
      cache[key] = val;
      return val;
    }
  };
}

const sum = (a, b) => a + b;
const memoizedSum = memoize(sum);

console.log(memoizedSum(2, 2)); // 運算 -> 4
console.log(memoizedSum(2, 2)); // 快取 -> 4


  1. 模擬私有變數
var counter = (function () {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function () {
      changeBy(1);
    },
    decrement: function () {
      changeBy(-1);
    },
    value: function () {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // 0
counter.increment();
counter.increment();
console.log(counter.value()); // 2
counter.decrement();
console.log(counter.value()); // 1

privateCounter 無法直接被外部存取,只能透過 incrementdecrement 修改,這就是閉包達成封裝的效果。


閉包的缺點

  • 潛在記憶體洩漏

    閉包會「捕獲」外部變數,這些變數就不會被垃圾回收(GC)回收,可能造成效能浪費。

    → 解法:只在需要的時候使用閉包。


為什麼要使用閉包?

你可能會問:「不就是要保存狀態嗎?為什麼不用全域變數或物件就好?」

這裡比較三種方法:

  1. 全域變數

    • 簡單,但不安全,容易被隨便修改
    var globalCount = 0
    func incrementGlobalCount() {
        globalCount += 1
        print(globalCount)
    }
    incrementGlobalCount() // 1
    incrementGlobalCount() // 2
    
    
  2. 物件導向(OOP)

    • 安全,也很常用,但需要定義完整的 class
    class Counter {
        var count = 0
        func increment() {
            count += 1
            print(count)
        }
    }
    
    let counterA = Counter()
    counterA.increment() // 1
    counterA.increment() // 2
    
    
  3. 閉包(函式導向)

    • 安全、輕量,特別適合封裝小範圍、簡單的狀態
    • 可以這樣想:我有一個行為(函式),但我希望這個行為能記住一些私有狀態」

參考資料

什麼是閉包 (Closure)?|ExplainThis

https://ithelp.ithome.com.tw/articles/10193009


上一篇
[DAY15] CORS 是什麼?
下一篇
[DAY17] JSON、FormData 和 URLSearchParams 是什麼?
系列文
30天一起搞懂Web觀念30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言