Closure

JavaScript 的記憶體管理

垃圾回收器(garbage collector) 系統

  • 追蹤記憶體分配的使用情況,以便自動釋放一些不再使用的記憶體空間
  • 全域變數關閉瀏覽器頁面,才會回收
  • 區域變數的值,沒有用到就會被自動回收

簡單理解

  • 閉包 = 內層函式 + 外層函式的變數
  • 當內層函式用到了外層的變數,就構成了閉包
  • 內部函式除了能夠取得函式外部的變數,還可以記住這個變數

範例 1

function outer() {
  const a = 1
  function f() {
    console.log(a)
  }
  f()
}
outer() // 1

範例 2

function outer() {
  let i = 1
  function fn() {
    console.log(i)
  }
  return fn
}
const fun = outer()
fun() // 1

應用 1 — 狀態保存

  • 通常會 return 一個匿名函式,可以取得到外層函式的變數
  • 用一個變數來取出回傳的函式
  • 因為 result 指向 count 這個函式(全域),myCount不會被回收
  • myCount 計算後的結果會被儲存下來

範例

//
function count() {
  let myCount = 0
  return function () {
    myCount++
    console.log(`被調用${myCount}次`)
  }
}
const result = count()
result() // "被調用1次"
result() // "被調用2次"

應用 2 — 緩存機制

  • 因為閉包原理,cache 變數可以被 return 的函式取得
  • 能夠用cache來放想要緩存的東西,在執行前先檢查是否存在快取
  • 可以避免重複計算
function expensiveFunction() {
  // 這裡是複雜的操作
  console.log('Executing expensive operation...')
  return 'Expensive Result'
}

function createCache() {
  let cache = {}

  return function (key, func) {
    if (!cache[key]) {
      cache[key] = func()
    }
    return cache[key]
  }
}

const cachedFunction = createCache()

// 首次調用,將計算結果緩存
// 輸出: Executing expensive operation... Expensive Result
console.log(cachedFunction('test', expensiveFunction))

// 第二次調用,直接從緩存回傳結果
// 輸出: Expensive Result(不會再次執行 expensiveFunction)
console.log(cachedFunction('test', expensiveFunction))

應用 3 — 模擬私有變數

  • 開發的程式碼內部細節,並不想讓外部來獲取時
  • 可以避免變數被直接修改,維持穩定性
function createBankAccount(initialBalance) {
  let balance = initialBalance // 私有變數

  return {
    deposit: function (amount) {
      balance += amount
      console.log(`存款後餘額:${balance}`)
    },
    withdraw: function (amount) {
      if (amount <= balance) {
        balance -= amount
        console.log(`提款後餘額:${balance}`)
      } else {
        console.log('餘額不足')
      }
    },
    getBalance: function () {
      return balance // 提供對私有變數的讀取方式
    }
  }
}

const account = createBankAccount(100)
account.deposit(50) // 存款後餘額:150
account.withdraw(20) // 提款後餘額:130
console.log(account.getBalance()) // 130
console.log(account.balance) // undefined,無法直接訪問私有變數

需注意

  • 當使用閉包時,因為變數會常駐在記憶體中不會清除
  • 若使用過多,可能導致內存泄露(memory leak)

參考資料

# JavaScript # 閉包