閉包

閉包 在 JavaScript

1 個練習

關於 閉包

_閉包_是 JavaScript 中的一種程式設計模式,它讓外層語彙作用域的變數可以在巢狀的程式區塊內使用。JavaScript 以透明的方式支援閉包,因此我們常常在不知道它們是什麼的情況下就用到了它們。

// Top-level declarations are global-scope
const dozen = 12;

// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
  return dozen * n;
}

_閉包_這個名稱在歷史上源自λ演算,並由 Scheme 推廣(來源),用來指稱函式中開放與封閉的變數綁定。

在 JavaScript 中使用閉包的理由

  1. 資料隱私 / 資料封裝

    • 與其他語言不同,在 2020 年時,還沒有辦法指定 私有 變數。因此可以用閉包來有效模擬 私有 變數(當時有一項引入私有變數標記法的提案,等你讀到這裡時,它可能已經成為標準了)。
    // Consider this function:
    
    const saveNumber = (number) => {
      // The saved value is unaccessible by the outer lexical scope.
      const value = number;
    
      // We can provide access to the primitive value with a function, but the original will never change
      return () => value;
    };
    
    // Attempting to set the variable outside of its lexical scope results in an error
    value = 42;
    
  2. 部分應用

    • 函式可以回傳函式;當被回傳的函式使用了建立它的那個函式的引數時,這就是用閉包進行部分應用的一個例子。有時這被稱為對函式進行 柯里化。
    // The arguments are applied one-by-one, using closures to arrive at the final result
    function partialBuildUri(scheme) {
      return function (domain) {
        return function (path) {
          return `${scheme}://${domain}/${path}`;
        };
      };
    }
    
    // A function could apply them all at once.
    function buildUri(scheme, domain, path) {
      return partialBuildUri(scheme)(domain)(path);
    }
    
    // Or apply a few to create a flexible system of functions for reuse.
    function buildHttpsExercismUri(path) {
      return partialBuildUri('https')('exercism.org');
    }
    

變數的語彙作用域

在任何程式語言中,閉包都與變數作用域密切相關。在 JavaScript 中,變數的語彙作用域有 3 種:

  1. 全域作用域:這些是在函式和區塊之外宣告的變數。
  2. 函式作用域:這些是在函式中使用var關鍵字宣告的變數。
  3. 區塊作用域:這些是在區塊({...})中使用let和const關鍵字宣告的變數。

在實務上,閉包通常出現在函式(或區塊)使用外層作用域的變數時。在 JavaScript 中,這是透明支援的,所以閉包常在不被特別提及的情況下使用。

函式作用域

var關鍵字定義的是函式作用域的變數。這表示用var定義的變數,在定義它的函式中任何位置,以及任何巢狀的語彙作用域(無論是函式或區塊)都可以使用。

{
  var five = 5;
}

// This will succeed because 'five' exists in the same scope
var tenTimes = five * 10;

區塊作用域

let和const關鍵字定義的是區塊作用域的變數。let定義的是可變的變數,它的值可以設定多次,也可以只定義而不初始化。const定義的變數必須在定義時初始化,之後它的值就不能再改變。如果const變數被設為原始值,它就不能被修改;如果const變數被設為參照值,例如_陣列_或_物件_,那麼參照本身不能改變,但陣列的內容可以被修改。

來看看這個區塊:

// 'two' is defined at the top-level, therefore available to the global-scope
const two = 2;

{
  // This succeeds, as 'two' is available from the outer lexical scope
  const oneLess = two - 1;
}

// This throws an error because oneLess is not defined outside of the previous inner lexical scope
console.log(oneLess);

雖然我們可以任意用大括號({...})來建立區塊,但最常看到它們出現在_條件式_、_函式_和_類別_的周圍。

const PI = 3.14;

// This function uses a closure for the value of PI (π)
function areaOfCircle(radius) {
  return PI * radius * radius;
}
透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 閉包