吉本集の個人ブログ
Web制作の技術について書いています。たまに日記も書きます。

【JS入門】関数について Vol.3(関数の入れ子、クロージャ)

2018年1月16日 / category : javascript

今回の記事では、関数を勉強する際に必ず出てくるクロージャについて解説していきます。
MDNでは、クロージャを「独立した (自由な) 変数を参照する関数」と表現しています。
クロージャをこれから解説していきますが、クロージャを使用することで新しい構文が出てくるわけではありません。クロージャとはどういう仕組みなのかを理解し、JavaScriptの知識を深めることを目的としてもらえればと思います。

関数の入れ子

クロージャを解説する前に、関数の入れ子について触れてみます。次のコードを見てみます。

sample.js
function funcA(){
	var num = 0;
	function funcB(){
		num++;
	}
	funcB();
	return num;
}
var value = funcA();
console.log( value );

コンソール画面を確認すると1が表示されています。上のコードを見ると、関数funcAの中に関数funcBが定義されています。これが関数の入れ子です。変数valueに関数funcAの実行結果を代入していますが、関数funcAでreturnしているのは、変数numになります。変数numの初期値に0が代入されていますが、returnする前に関数funcBを実行しています。関数funcBでは、変数numに「++」をしています。これは、変数numに+1するという演算子です。これで0+1となり、結果、変数numは1になり、returnされています。
ここで確認しておきたい点は、関数funcBでは、関数funcBの外で定義している変数numを参照できている点です。
ただし、前の記事でスコープの話をしましたが、これは自然なことです。

続いて、上のコードを次のように少し変更してました。

sample.js
function funcA(){
	var num = 0;
	function funcB(){
		num++;
		return num;
	}
	return funcB();
}
var value = funcA();
console.log( value );

上のコードをコンソール画面で確認すると先ほどと同様1が表示されています。変更された点として、関数funcAのreturn文で関数funcBが実行され、関数funcBの中にさらにreturn文が追加されました。関数funcBが実行されると変数numが+1され、変数numがreturnされます。また、関数funcAが実行されると、関数funcBの実行結果がreturnされます。変数valueには関数funcAの実行結果が代入されますので、結果1が代入されることになります。

上のコードを少し変更してみます。

sample.js
function funcA(){
	var num = 0;
	function funcB(){
		num++;
		return num;
	}
	return funcB;
}
var value = funcA();
console.log( value() );

こちらも同様に1が表示されます。変更点として、関数funcAのreturnで関数funcB自体を返しています。(先ほどは関数funcBの実行結果を返していました。)また、関数funcB自体をreturnすることで変数valueも関数になるため、console内で変数valueを実行することで、関数funcBが実行され、結果、変数numがreturnされています。
実はこれがクロージャです。

クロージャとは

記事の最初にクロージャについて、MDNでは、クロージャを「独立した (自由な) 変数を参照する関数」と表現しています。と紹介しました。
上のコードで例えると「独立した (自由な) 変数」が変数numにあたります。「参照する関数」は変数valueにあたります。
変数numは、関数funcA内で定義されており、通常はトップレベルからは参照できません。これは前の記事で解説しました。関数の中で定義している変数はローカル変数となり、その関数の外からは参照できません。
ただしクロージャを使うと、関数の外からローカル変数を参照することができるようになります。
この環境をクロージャと呼びます。また、変数valueがクロージャになったという表現の仕方もできます。

クロージャの特長 1

クロージャの環境では、その関数内のローカル変数の状態を保持したまま使用することができます。
確かめてみましょう。

sample.js
function funcA(){
	var num = 0;
	num++;
	return num;
}
console.log( funcA() );
console.log( funcA() );

上のコードをコンソール画面で確認すると1,1と表示されます。この結果は想像できるかと思います。関数funcAを2回実行していますが、実行するたびに変数numが定義されますので、1回目に実行された結果を2回目の実行時で継続されるわけではありません。
それでは先ほどのクロージャのコードで、変数valueを2回実行してみます。

sample.js
function funcA(){
	var num = 0;
	function funcB(){
		num++;
		return num;
	}
	return funcB;
}
var value = funcA();
console.log( value() );
console.log( value() );

上のコードをコンソール画面で確認すると1,2と表示されます。関数funcAのローカル変数numの状態が保持されていることがわかります。
これがクロージャの最大の特長と言っても良いかもしれません。変数num自体は、関数funcAのローカル変数になっており、その外から直接参照することができません。ただし、クロージャとなった関数valueを実行することで変数numを変更することができます。

クロージャの特長 2

クロージャを使うことで、グローバル変数を減らすことができるという特長があります。次のコードを見てみます。

sample.js
var num = 0;
function funcA(){
	num++;
	return num;
}
console.log( funcA() );
console.log( funcA() );

上のコードをコンソール画面で確認すると1,2と表示されます。これは先ほどのクロージャのコードと結果が同じです。では、クロージャを使う必要がないのでは・・・と考えるかもしれませんが、上のコードとクロージャのコードの違いは、変数numがグローバル変数かローカル変数かどうかです。
実務でプログラムする際、グローバル変数は極力使用しないほうが良いです。それは複雑なコードになればなるほど、変数の衝突が起きやすくなってしまうからです。これをクロージャ環境にすることで変数同士の衝突を減らすことができます。理由は、何度も出てきていますが、関数の外からはローカル変数に参照できないからです。
よって、グローバル変数を減らすことができるという仕組みもクロージャの特長になります。

以上が、クロージャについての解説でした。今回は新しい構文は出てきていませんが、クロージャの仕組みはJavaScriptを構築していく上で、大変勉強になる仕組みです。できるだけわかりやすく解説したつもりですが、私の日本語力の問題もあり、この記事だけでは伝わりにくいかもしれません。もしこの記事で理解できない場合は、他の記事なども参考にしてクロージャの知識をしっかり身につけましょう。