JavaScript - 閉包

Himmelbleu發表於2024-08-30

閉包概念

閉包(Closure)是 JavaScript 中的一個核心概念,它指的是函式和其外部環境(或詞法作用域)之間的組合。簡單來說,閉包是指當一個函式在其外部作用域中引用了變數時,該函式和這些變數的組合形成了一個閉包

閉包的表現

閉包通常是由一個外部函式包裹一個內部函式,且返回內部函式,從而使得內部函式可以訪問外部函式作用域中的變數。但這並不是閉包的唯一形式。

閉包的本質是 函式“記住”了它所在的詞法作用域,即使這個函式是在其定義的作用域之外執行的。

常見的閉包

function outerFunction() {
    let count = 0;
    return function innerFunction() {
        count++;
        console.log(count);
    };
}

const closure = outerFunction();
closure(); // 輸出 1
closure(); // 輸出 2

不返回函式的閉包

即使外部函式不返回內部函式,閉包仍然存在,只要內部函式訪問了外部函式中的變數。例如,在事件監聽器或定時器中。

function setupClickListener() {
    let count = 0;
    document.getElementById('myButton').addEventListener('click', function() {
        count++;
        console.log(count); // 訪問了外部函式的變數 count
    });
}

setupClickListener();

雖然 setupClickListener 沒有返回內部函式,但內部函式仍然形成了閉包,因為它引用了外部函式中的 count 變數。

定時器中的閉包

function startTimer() {
    let count = 0;
    setInterval(function() {
        count++;
        console.log(count); // 訪問外部函式的 count
    }, 1000);
}

startTimer();

使用場景

資料封裝

閉包允許建立私有變數,這些變數在函式外部無法直接訪問。透過閉包,可以建立具有私有狀態的物件,從而實現資料封裝。例如:

function createCounter() {
  let count = 0; // count 是私有變數
  return function () {
    count++;
    return count;
  };
}

const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2

const counter2 = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

閉包可以用來建立模組化的程式碼結構,使得模組內的變數和函式不會汙染全域性名稱空間。例如:

const Module = (function () {
  let privateVar = "I am private";

  function privateMethod() {
    console.log(privateVar);
  }

  return {
    publicMethod: function () {
      privateMethod();
    }
  };
})();

Module.publicMethod(); // 輸出 "I am private"

閉包的關鍵在於它能夠“記住”外部函式的狀態,即使外部函式已經執行完畢。這個特性使得閉包在 JavaScript 中非常有用,尤其是在涉及到資料隱私、狀態管理和函式工廠等方面。

延遲執行

閉包可以用來延遲執行某些程式碼,尤其在處理非同步操作時。例如:

function delayedGreeting(name) {
  return function () {
    console.log(`Hello, ${name}!`);
  };
}

const greetJohn = delayedGreeting("John");
setTimeout(greetJohn, 1000); // 1秒後輸出 "Hello, John!"

事件處理

閉包常用於事件處理程式,以便訪問事件處理程式內的資料。例如:

function setupButton(buttonId) {
  const button = document.getElementById(buttonId);
  let count = 0;

  button.addEventListener("click", function () {
    count++;
    console.log(`Button clicked ${count} times`);
  });
}

setupButton("myButton");

記憶體洩漏

閉包可以導致記憶體洩漏,因為閉包會保留對其外部作用域的引用。如果閉包引用的變數或函式沒有被及時釋放,垃圾回收機制可能不會回收這些記憶體,從而導致記憶體洩漏。

例如,在事件監聽器中使用閉包,如果不及時移除事件監聽器,可能會導致記憶體無法被釋放:

function createListener() {
    let element = document.getElementById('myElement');
    element.addEventListener('click', function() {
        console.log('Clicked!');
    });
}
createListener();

這個例子中,如果不手動移除事件監聽器,即使元素被刪除了,閉包仍然會保留對它的引用,導致記憶體洩漏。

避免過度依賴閉包來儲存狀態

可以透過將狀態和邏輯分離來減少閉包的使用。例如,使用物件、類或者資料結構來管理狀態,而不是全部放入閉包中。

class Counter {
    constructor() {
        this.count = 0;
    }

    increment() {
        this.count++;
        return this.count;
    }
}

const counter = new Counter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2

相關文章