クロージャーをJavaScriptで理解する・使い方

JavaScript

クロージャーとは、別名「関数閉包」と呼ばれ、内部スコープから外部スコープの変数を参照する関数です。クロージャーは、外側のスコープをその内側のスコープ内に保持します。基本的にクロージャーはFunctionオブジェクトになります。

悩見坂 楓
悩見坂 楓

くろーじゃーってデザイナーには無縁だし

著者
著者

デザイナーに向けて書いてないからね

オブジェクト(Object型オブジェクトですが……)については別途まとめてありますので、興味ある方は併せてご参照ください。先に読まれた方がオブジェクトの操作への理解が深まりますが、オブジェクトについては大丈夫な方はそのまま続けてください。

「外側」「内側」と説明してもイメージが沸かないと思いますので、サンプルコードを読み解くのが理解への一番の近道です。外側の関数のことを「エンクロージャー」と言ったりします。

クロージャーを使うメリットとしては、グローバル変数を少なくしたり、コールバック関数の可読性を高め、簡素化することが挙げられます。

クロージャーとは何かを理解するためにサンプルコードを掲載しつつ、説明していきます。

クロージャーの最も簡単なサンプルコード

何事も最小単位で物事を捉えると理解が早くなりますね。以下に最小のクロージャーを掲載します。

function enClosure() {
  let localVariable = 'Hades'; // ローカル変数を宣言
  function closureSample() { // クロージャー内の関数
    console.log('localVariable : ' + localVariable);
  }
  return closureSample; // 関数を返す
}

一番外側を囲っているenClosure()メソッドがエンクロージャーになります。先ほど言っていた「外側」の関数です。

このenClosure()メソッド内にあるclosureSample()メソッドが「内側」の関数ですね。この例ではclosuseSample()でローカル変数のlocalVariableをコンソール出力しているだけです。

肝心なのは外側のenClosure()がreturnでclosureSampleメソッドを返している点です。returnは変数だけでなく、関数(Functionオブジェクト)も返すことが出来るんですね。

もちろん、この状態ではコンソールに何も出力されませんので、下記のようにenClosure()を呼び出して実行してあげる必要があります。

let enc01 = enClosure(); // クロージャーの初期化
enc01(); // 初期化したオブジェクトでクロージャー内の関数を実行

あれ、enCosure()だけじゃ実行されないの?と思うかもしれませんが、実行されません。

retrunしているのがfunction名(Functionオブジェクト)なので、上の例ではenc01に代入されているのはenClosure()内のclosureSampleという単なるFunctionオブジェクトなのです。

Functionオブジェクトはそれ単体では動作しませんので、「()」を付けて実行させているんですね。

ちなみになんですが、この「()」をenc01に付けなくても動作させる方法があります。先ほどのコードを少し変えてみます。

function enClosure() {
  let localVariable = 'Hades';
  function closureSample() {
    console.log('localVariable : ' + localVariable);
  }
  return closureSample(); // 実行済みの関数を返す
}

returnの部分に注目してください。先ほどはreturn closureSample;としていましたが、今回はreturn closureSample();としました。

これであれば、enc01();は不要で、let enc01 = enClosure();を記述した時点で即時実行され、コンソール出力に「localVariable : Hades」と表示されます。

だだし、この場合ではクロージャーを実行するタイミングが、オブジェクトを宣言した時に限られてしまいます。実用的なのは、(面倒ではありますが)2行でenc01();を使うパターンに統一しましょう。全体のイメージは以下のようになります。

function enClosure() {
  let localVariable = 'Hades';
  function closureSample() {
    console.log('localVariable : ' + localVariable);
  }
  return closureSample;
}

let enc01 = enClosure();
enc01();

特に難しいことはありませんね。

(参考:https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures

悩見坂 楓
悩見坂 楓

なんか初心者の私でも書けそうな気がしてきた

著者
著者

とりあえず書きまくるのが最初の一歩ですね

クロージャーと切っても切れないレキシカルスコープ

悩見坂 楓
悩見坂 楓

また訳の分からない単語が出てきた……

著者
著者

分からないことは自分なりにどこかにまとめておくと後で見返して覚えやすくなりますよ

レキシカル(Lexical)スコープは、静的スコープ、字句スコープとも呼ばれ、親関数(エンクロージャー)で定義された変数を指します。Lexicalは日本語で「単語の」や「語彙的な」という意味です。字句のみでスコープが決定されるためLexical Scopeと呼ばれています。

レキシカルスコープはエンクロージャーが定義された時点で決定されます。

function enClosure() {
  let localVariable = 'Hades';
  function closureSample() {
    console.log('localVariable : ' + localVariable);
  }
  return closureSample;
}

let enc01 = enClosure();
enc01();

先ほどの例ですとlocalVariableclosureSample()のスコープ内にあります。スコープというのは変数が扱えるコード内の有効範囲のことです。

closureSampleにはローカル変数は存在しませんが、loalVariableにはアクセスが可能です。enClosereオブジェクト内にある変数がクロージャーから参照できるということを押えておきましょう。

プライベートメソッド的な使い方

例えばJavaには同じクラス内からのみアクセスできるメソッドを実装することが出来ます。カプセル化とよく言いますが、JavaScriptでも同様の実装が可能です。
(参考:https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures

let sumupSample = (function() {
  let countNumber = 0;
  function changeBy(val) {
    countNumber += val;
  }
  return { // returnは配列形式でも書けることが重要
    incCountNumber: function() {
      changeBy(1);
    },
    decCountNumber function() {
      changeBy(-1);
    },
    getCountNumber: function() {
      return countNumber; // returnの中にreturnも入れれます
    }
  };
})();

console.log(sumupSample.getCountNumber());

.getCountNumber()の部分が分からない場合は、JavaScriptのオブジェクト型の記事を参考にしてみてください。この例であれば、特に加算も減算もされない状態を返しているので「0」と出力されます。

加算のメソッドincCountNumberを利用する場合は、

sumupSample.incCountNumber();

減算のメソッドを使う場合は、

sumupSample.decCountNumber();

のように呼び出します。オブジェクトを理解していれば難しいことはありません。

クロージャの多用はパフォーマンス低下を招く

functionにfunctionをむやみに入れる(ネストする)とクライアントPCに無駄に負荷をかけることになります。

クロージャーの真価が問われないような(ローカル変数を返すだけとか)場合には、prototypeを利用することが推奨されています。

function MyObject(name, age) {
  this.name = name.toString();
  this.age = age.parseInt();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.age;
};

prototypeはオブジェクトを継承・拡張するJavaScriptの仕組みです。説明が長くなるため、また別稿で紹介することにします。

MyObject関数の中にはfunctionが入っていないことが重要なポイントです。

書き方は単純で、上記の例では

MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.age;
};

のように書きます。this.namethis.ageを参照できるのは、MyObjectを継承しているから変数にアクセスできるんですね。

このprototypeはメモリの省エネにつながりますので、積極的に利用することをおすすめします。

タイトルとURLをコピーしました