JavaScript 閉包(Closure)新手入門解析教學


JavaScript 的閉包(Closure)是一個重要的概念,它可以讓開發者創建一個私有的作用域,避免全局命名空間污染,同時也可以保存變量的值,使其在函數之外仍然可以訪問。

閉包是由一個函數和其環境變量組成的,這些變量可以被函數訪問,但不能被外部訪問。當函數返回時,閉包會保留這些變量的值,即使函數已經執行完畢,這些變量也會被保留下來。

以下是一個簡單的範例,演示了閉包的基本用法:

function outer() {
  var count = 0;
  function inner() {
    count++;
    console.log(count);
  }
  return inner;
}

var closureFunc = outer();
closureFunc(); // 1
closureFunc(); // 2
closureFunc(); // 3

在這個範例中,outer 函數中創建了一個 count 變量和一個 inner 函數,inner 函數可以訪問外部的 count 變量,並且每次被呼叫時都會將 count 加 1。最後,outer 函數返回 inner 函數本身,而不是 inner 函數的返回值。

當我們呼叫 outer 函數時,它會返回 inner 函數的參考,並且將其存儲在 closureFunc 變量中。當我們呼叫 closureFunc 函數時,它仍然可以訪問 count 變量,並將其值加 1。

由於閉包保存了內部環境的狀態,因此它可以用於許多實用的情況,例如實現私有屬性或方法。

function counter() {
  var count = 0;
  function increment() {
    count++;
  }
  function decrement() {
    count--;
  }
  return {
    increment: increment,
    decrement: decrement
  };
}

var c = counter();
c.increment();
c.increment();
c.decrement();
console.log(c.count); // undefined

在這個範例中,counter 函數創建了兩個內部函數 increment 和 decrement,它們可以訪問 count 變量。然後,counter 函數返回一個對象,該對象包含 increment 和 decrement 方法的引用。由於 count 變量是在函數內部創建的,因此無法從外部訪問它,這樣我們就可以實現私有屬性或方法了。

閉包是 JavaScript 中一個重要的概念,但是它也可能會導致內存泄漏(Memory Leak)的問題。當一個閉包中引用了外部作用域的變量時,這些變量的值不會被垃圾回收,即使它們已經不再被使用。如果閉包被保存在全局變量中,這些變量的值將永遠不會被釋放,這就會導致內存泄漏。

以下是一個例子:

var elements = document.getElementsByTagName('div');
for (var i = 0; i < elements.length; i++) {
  elements[i].addEventListener('click', function() {
    console.log(i);
  });
}

在這個例子中,我們創建了一個事件監聽器,當 div 元素被點擊時,會在控制台中輸出變量 i 的值。由於事件監聽器是在閉包中創建的,所以它可以訪問外部的 i 變量。然而,由於 i 是用 var 關鍵字聲明的,所以它是全局變量,而不是區域變量。這意味著當我們單擊 div 元素時,i 的值已經是 elements.length,而不是我們期望的值。這是因為 i 的值是在循環過程中逐步增加的,最終變成了 elements.length。

要解決這個問題,我們需要將變量 i 定義為區域變量,而不是全局變量。可以使用 let 或 const 關鍵字來定義 i,這樣每次循環時都會創建一個新的變量。

var elements = document.getElementsByTagName('div');
for (let i = 0; i < elements.length; i++) {
  elements[i].addEventListener('click', function() {
    console.log(i);
  });
}

這個例子中,我們使用 let 關鍵字來聲明變量 i,這樣每次循環時都會創建一個新的變量,並且它的作用域僅限於循環內部。這樣,每個事件監聽器都可以訪問其對應的變量 i,而不會產生混亂。

閉包是 JavaScript 中一個強大的概念,它可以幫助開發者實現許多有用的功能,同時也需要注意避免內存泄漏的問題。掌握閉包的概念,可以更好地理解許多 JavaScript 中的高級概念,例如回調函數、Promise 和 async/await 等。

以下是一個簡單的閉包例子,用於計算某個函數的執行時間:

function timer(func) {
  return function() {
    var start = new Date().getTime();
    var result = func.apply(this, arguments);
    var end = new Date().getTime();
    console.log('Execution time: ' + (end - start) + 'ms');
    return result;
  };
}

function fibonacci(n) {
  if (n === 0 || n === 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

var timedFibonacci = timer(fibonacci);

console.log(timedFibonacci(40));

在這個例子中,我們創建了一個名為 timer 的函數,它接受一個函數作為參數,並返回一個新的函數。這個新的函數是一個閉包,可以訪問 timer 函數的作用域,包括 start、end 和 result 變量。當我們調用這個新的函數時,它會先記錄當前時間(start),然後執行原始函數,並記錄執行結束時間(end)。最後,它會計算執行時間並輸出到控制台。

我們可以使用這個 timer 函數來計算 fibonacci 函數的執行時間,並將計時版本的 fibonacci 函數保存在 timedFibonacci 變量中。當我們調用 timedFibonacci(40) 時,它會計算 fibonacci(40) 的值並輸出執行時間。

閉包是一個非常強大的概念,它可以使 JavaScript 開發者實現許多有用的功能。了解閉包的概念和如何使用它們是成為高級 JavaScript 開發者的關鍵。希望本文能夠幫助初學者更好地理解閉包的概念和用法。

#javascript #closure #閉包







你可能感興趣的文章

2. 時間複雜度 (下篇)

2. 時間複雜度 (下篇)

MTR04_1005

MTR04_1005

MTR04_0624

MTR04_0624






留言討論