クロージャ

1つの関数を、複数の関数として使えたり、関数を途中で変更できると便利です。

本ページでは、クロージャの使い方について説明します。

クロージャとは

クロージャとは、ネスト(入れ子)された内側の関数のことを言います。単に内側の関数というだけでなく、クロージャには以下の特徴があります。

  • 外側の関数(エンクロージャ)が実行された時に作成される。
  • エンクロージャで宣言された変数を使える。
  • エンクロージャで宣言された変数の値を変更できる。

下の2つは、スコープチェーンによるものです。

この特徴を利用して、外側の関数が1つであっても、変数の値が異なる複数のクロージャを作成することができます。

クロージャの説明

上の図では、外側の関数で宣言したxを使ってクロージャを作成しています。その際、クロージャ1はx = 10、クロージャ2はx = 20が使われています。このため、yの値が同じ5であっても、クロージャ1を実行すると結果は15、クロージャ2を実行すると結果は25と、異なる結果を返すことができます。

また、クロージャ1のxを30に変更するなど、値を後で変更することもできます。

クロージャの作成方法

クロージャの作成例は、以下のとおりです。

【クロージャの作成例】
function func1(arg) {
  let x = arg + 2;
  function func2(y) {
    return x + y;
  }
  return func2;
}

const closure1 = func1(8);
alert(closure1(5));
const closure2 = func1(18);
alert(closure2(5));

変数xは、エンクロージャのfunc1で宣言しています。func1(8)を実行した時、クロージャclossure1が作成されます。その関数はfunc2ですが、エンクロージャで宣言しているxも使えます。

func1(8)を実行した時、xはarg + 2から10になります。このため、alert(closure1(5));で表示される数字は、15になります。

closure2では、xの値は20になります。このため、alert(closure2(5));で表示される数字は、25になります。

言い換えれば、closure1はxが10の値を持つfunc2、closure2はxが20の値を持つfunc2ということです。

エンクロージャで宣言された変数の値を変える

先の説明で、クロージャの特徴としてあげた「エンクロージャが実行された時に作成される」、「エンクロージャで宣言された変数を使える」というのはご理解いただけたと思います。

次は、「エンクロージャで宣言された変数の値を変更できる」という特徴についてです。以下のスクリプトがあったとします。

【エンクロージャで宣言された変数の値を変更する例】
function func1(arg) {
  let x = arg + 2;
  function func2(y) {
    return x = x + y;
  }
  return func2;
}

const closure1 = func1(8);
alert(closure1(5));
alert(closure1(5));

func1の中で、x + y部分をx = x + yに変更しています。最初のalert(closure1(5))は、xが10のためx + yが実行されて15が表示されます。この時、xに15が代入されます。

次のalert(closure1(5))は、xが15に変更されているため、結果として20が表示されます。

このように、エンクロージャ側で宣言された変数の値は変更できるため、同じ引数を使ってクロージャを実行しても、結果を変えることができます。

クロージャのメリット

最初に示したスクリプトを、以下のように2つの関数に分けたとします。

【ローカル変数が消えてエラーになる例】
function func1(arg) {
  let x = arg + 2;
}

function func2(y) {
  return x + y;
}

func1(8);
alert(func2(5));

func1とfunc2をネストにしていません。

func1(8)を実行した時、xは10になります。しかし、func2(5)を実行した時、エラーになります。これは、xがローカル変数のため、他のスコープで使えないためです。

func2をネストしてクロージャを作成すれば、このxが使えるメリットがあります。また、このxの値はグローバル実行コンテキスト(関数の外)で、間違ってx = 20などを実行しても変更されないのもメリットです。以下は、例です。

【クロージャ外でxを変更した例】
function func1(arg) {
  let x = arg + 2;
  function func2(y) {
    return x + y;
  }
  return func2;
}

const closure1 = func1(8);
x = 20;
alert(closure1(2));

func1の中で変数xを使っています。x = 20は、グローバル変数として20の値が代入されますが、closure1で使われるxは10のままです。このため、最後のalert(closure1(2))で表示されるのは、10 + 2で12になります(20 + 2ではない)。

無名関数での記述

クロージャは、無名関数でも記述できます。

【クロージャを無名関数で記述した例】
function func1(arg) {
  let x = arg + 2;
  return function(y) {
    return x + y;
  }
}

const closure1 = func1(8);
alert(closure1(5));

今まではfunc2という関数名を使っていましたが、それを省略しています。無名関数については、「関数式の使い方」の無名関数をご参照ください。

クロージャの実用例

実際の利用がイメージできるように、実用例を示します。

最初に実行例です。

上の段は、1枚だけ裏返しで表示されています。下の段は、2枚裏返しになっています。上の段と下の段で、3枚目は同じトランプが表示されていると思います。

これは、クロージャでトランプの数字を保持しつつ、2回クロージャを実行しているためです。

スクリプトは、以下のとおりです。

【前の状態を保持しつつ1枚ずつトランプを裏返す例】
<div id="test1">
</div>

<script>
function func1(x) {
  let z = new Array;
  for ( let i = 1; i <= x ; i++ ) {
    z[i] = Math.floor( Math.random() * 10 ) + 1;
  }
  return function(y) {
    z[y] = 99;
    for ( let i = 1; i <= x; i++ ) {
      test1.innerHTML = test1.innerHTML + '<img src="image/trump/1' + z[i] + '.png" alt="トランプ">';
    }
  }
}

const test1 = document.querySelector("#test1");
const closure1 = func1(3);
closure1(1);
test1.innerHTML = test1.innerHTML + "<br>";
closure1(2);
</script>

const closure1 = func1(3)を実行した時に、zの配列には1から10の間でランダムな数字が代入されます。

closure1(1)を実行した時に、z[1]には99が代入されます。これが、トランプの裏側を示します。その後、zの値に対応したトランプを表示する処理をしています。

closure1(2);を実行した時、z[2]に99が代入されます。ただし、z[1]とz[3]の値は変わりません。このため、2枚が裏返しで、3枚目は最初に実行した時と同じトランプが表示されることになります。

このように、以前の状態を保ちながら、次の結果を表示させることができます。また、const closure2 = func1(5)を実行すると、トランプを5枚表示するクロージャがすぐに作成できます。