クロ

クロージャー の 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);

波括弧({...})は、ブロックを作るために自由に使えますが、たいていは_if文_、関数、_クラス_を囲む形で見かけます。

const PI = 3.14;

// This function uses a closure for the value of PI (π)
function areaOfCircle(radius) {
  return PI * radius * radius;
}
GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

クロージャーを学習する