閉包 Closure

  1. 閉包是一種在計算機科學和程式設計中的概念,通常與函數相關。
  2. 它捕獲了函數定義時所在的作用域內的變數,並允許這些變數在函數執行時仍然可用,
    即使它們的作用域已經結束。

形成方式

  1. 函數內部定義函數
    當在一個函數內部定義另一個函數時,內部函數可以捕獲外部函數作用域內變數,
    只要內部函數實際上使用了外部函數的變數,內部函數及其捕獲的變數就形成了一個閉包。
    如果內部函數不使用外部函數的變數,則不會形成閉包。

  2. 函數作為參數傳遞或返回值
    如果一個函數接受另一個函數作為參數,並在內部使用它,
    或者一個函數將另一個函數作為返回值返回,
    那麼該內部函數通常會捕獲包含它的外部函數的作用域,形成閉包。
    這種情況通常會形成閉包,因為內部函數需要訪問外部函數的作用域內的變數,以便正確執行。

// JavaScript程式碼示例

// 在outer的作用域內定義了一個內部函數inner
// 內部函數捕獲了外部函數的局部變數x,閉包包含了變數x、inner函式

function outer(x) {
  function inner(y) {
    return x + y; 
  }

  return inner; // 外部函數返回內部函數形成閉包
}

const closure = outer(10); // 調用outer,得到一個閉包
console.log(closure(5)); // 調用閉包,輸出15,因為它仍然可以訪問x的值

使用情境

閉包的存在使得在某些情況下可以實現高級的程式設計技巧,例如函數工廠(function factory)、私有變數(private variables)和記憶化(memoization)。

封裝數據和行為

通過使用閉包,您可以創建一個函數,該函數封裝了某些數據(變量)以及對這些數據的操作(函數),從而隱藏了內部狀態,並提供了一種更安全的方式來訪問和修改數據。

function createCounter() {
  let count = 0;
  
  return {
    increment: function() {
      count++;
    },
    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1

函式工廠

函數工廠是一種強大的編程技術,使您能夠創建可複用的函數或對象,以滿足各種不同的需求和場景。它有助於提高代碼的可維護性、可擴展性和可重用性

function greetPrefix(prefix) {
  return function(name) {
    console.log(`${prefix}, ${name}`);
  };
}

const greetHello = greetPrefix("Hello");
greetHello("Alice"); // 输出 "Hello, Alice"

維護狀態

閉包允許函數保持狀態,並在不同的時刻訪問和修改相同的變數,這對於維護狀態信息非常有用。例如,計數器函數可以使用閉包來記錄狀態,即每次調用時遞增計數。

function setupClickHandler() {
  let count = 0;
  
  document.getElementById("myButton").addEventListener("click", function() {
    count++;
    console.log(`Button clicked ${count} times.`);
  });
}

setupClickHandler();

回調和事件處理

許多回調函數和事件處理函數都是閉包。它們可以捕獲外部範圍內的上下文信息,並在稍後的時間執行。

function fetchData(url, callback) {
  fetch(url)
    .then(response => response.json())
    .then(data => {
      callback(data);
    });
}

let result;
fetchData("https://example.com/api/data", function(data) {
  result = data;
  console.log(result);
});

模塊化

通過使用閉包,您可以模擬私有變量和方法,從而創建模塊化的代碼結構,以防止全局作用域的污染。

const module = (function() {
  let privateData = 42;
  
  function privateFunction() {
    return privateData;
  }
  
  return {
    getPrivateData: privateFunction
  };
})();

console.log(module.getPrivateData()); // 输出 42

商業應用

用戶身份驗證和授權系統

function createAuthenticator(strategy) {
  return function (user) {
    // 根據策略執行身份驗證和授權操作
    return strategy.authenticate(user);
  };
}

// 創建基於用戶名密碼的身份驗證策略
const usernamePasswordAuthenticator = createAuthenticator({
  authenticate: function (user) {
    // 執行用戶名密碼驗證邏輯
    // 返回 true 或 false 表示是否通過驗證
  },
});

// 創建基於社交媒體登錄的身份驗證策略
const socialMediaAuthenticator = createAuthenticator({
  authenticate: function (user) {
    // 執行社交媒體登錄驗證邏輯
    // 返回 true 或 false 表示是否通過驗證
  },
});

// 在登錄過程中使用不同的身份驗證策略
const user = { username: "user123", password: "password123" };
if (usernamePasswordAuthenticator(user)) {
  // 用戶通過身份驗證
  // 執行授權操作
} else {
  // 用戶未通過身份驗證
  // 處理身份驗證失敗
}

電商產品管理

function createProduct(name, price) {
  return {
    name: name,
    price: price,
    getDescription: function () {
      return `Product: ${this.name}, Price: $${this.price}`;
    },
  };
}

// 創建不同類型的產品對象
const product1 = createProduct("Laptop", 999.99);
const product2 = createProduct("Smartphone", 599.99);

console.log(product1.getDescription()); // 輸出 "Product: Laptop, Price: $999.99"
console.log(product2.getDescription()); // 輸出 "Product: Smartphone, Price: $599.99"

在線預訂系統

function createReservation(customer, roomType, checkInDate, checkOutDate) {
  return {
    customer: customer,
    roomType: roomType,
    checkInDate: checkInDate,
    checkOutDate: checkOutDate,
    getTotalCost: function () {
      // 計算預訂的總費用
      // 根據客戶、房間類型和日期計算費用
    },
  };
}

// 创建预订对象
const reservation1 = createReservation("Alice", "Double Room", "2023-09-10", "2023-09-15");
const reservation2 = createReservation("Bob", "Single Room", "2023-09-12", "2023-09-14");

console.log(reservation1.getTotalCost()); // 輸出預訂1的總費用
console.log(reservation2.getTotalCost()); // 輸出預訂2的總費用