【JavaScript】クロージャーを使って再利用しやすくメンテナンス性の高いコード書こう!
2015/11/17
クロージャーって難しい!?実は簡単です。
クロージャーとは何か?
クロージャーを使うことで何ができるようになるのか?
詳しく解説しているサイトは沢山あるけど、詳しいが故に初心者だと理解しにくいと思ったので記事にしました。
(クロージャーで検索してくる人に全くの初心者はいないと思うのである程度知識がある前提で書きます。また、他のサイトのクロージャーの解説も読んでみてください。似た情報でも多角的に情報を得ることで理解が深まります。)
・・・関数内部の変数を永続的に保持することができる。これが答え。
(通常、関数は呼び出されるたびに関数内部の変数はリセットされる。例えば「関数自身が何回呼び出されたか」といった情報を記憶することはできない(関数外部の変数を利用する必要がある)。)
例えるならクラスベースのオブジェクト指向言語のインスタンス(プライベート)変数みたいなものですね。
クロージャーを使えばコードがすっきり!メンテナンス性が飛躍的に向上!
プログラム初心者の方は「クロージャーなんて使わなくてもグローバル変数使えば解決じゃない?」と思うかも知れません。
確かにグローバル変数は便利です。クロージャーなんて使わなくても大概の物は作れます。
しかし、グローバル変数の数が100、200となったらどうでしょう?
知らぬ間に変数名が重複して意図しない動作をするなんて事態になりかねません。
数が多くなれば変数名も複雑なものになるでしょう。名前を考えるのも一苦労です。
クロージャーを使えば変数名の重複や命名に悩まされることはありません。
簡潔にすることでバグに悩まされる可能性が大幅に減ります。
また、クロージャーを使うことで関数外の変数を参照する必要性が減る為、他のコードと切り離すことが容易(疎結合)になります。そうすることで再利用しやすくなったりメンテナンス性が向上したりと様々な恩恵が得られます。
クロージャーのサンプルコードを失敗例を交えてご紹介!
自身が何回実行されたかを表示する関数showMessage()を例に解説していきます。
まずは根本的に失敗している例です。
/* 関数が呼び出すたびに変数countの値を増やす関数(失敗例) */ function showMessage(){ var count = 0; count++; alert("この関数は" + count + "回実行されました"); } showMessage(); //この関数は1回実行されました showMessage(); //この関数は1回実行されました showMessage(); //この関数は1回実行されました //countが毎回リセットされちゃう!あたりまえだけど。
上記サンプルではshowMessage()自身のカウント回数を増やすという意図でshowMessage()のプロパティとして変数countを関数内部に定義していますが、showMessage()が呼ばれるたびに変数countが初期化されて正常にカウントされません。失敗です。
続いては変数countの初期化を関数外部で行うようにしたサンプルです。
/* 関数が呼び出すたびに変数countの値を増やす関数 変数countの初期は関数外部で行う。 */ function showMessage(){ count++; alert("この関数は" + count + "回実行されました"); } //関数外部でcountを初期化 var count = 0; showMessage(); //この関数は1回実行されました showMessage(); //この関数は2回実行されました showMessage(); //この関数は3回実行されました /* 正常に動くが変数countが関数外部に定義されている為、 他の関数から変数countを操作される可能性がある。 また、showMessage()を他プロジェクトに移植する場合、 変数countをコピペし忘れる可能性がある。 */
正常に動作しています。ただしこのプログラムは大きな問題を抱えています。
変数countはshowMessage()の状態を表すものなのに関数外部に定義しています。
これでは変数countは他の関数から操作される危険や、showMessage()を移植する際に変数countをコピペし忘れる恐れがあります。
また、開発規模が大きくなった場合、変数countは知らぬ間にコンフリクト(変数名の重複)する可能性もあります。先述したグローバル変数の危険の一つですね。
最後にクロージャーを使った例です。
/* 以下のコードがクロージャー。 永続的に保持したい変数countとshowMessage()をreturnする為の 関数getFunctionShowMessage()を定義。 */ function getFunctionShowMessage(){ var count = 0; return showMessage; function showMessage(){ count++; alert("この関数は" + count + "回実行されました"); } } /* showMessage()は隠蔽されている為外部からは呼び出せない。 getFunctionShowMessage()によってshowMessage()を取得する。 ちなみに変数showMessageとshowMessage()は同名だが、 showMessage()はgetFunctionShowMessage()内部に定義されている為コンクリフト(競合)しない。 */ var showMessage = getFunctionShowMessage(); showMessage(); //この関数は1回実行されました showMessage(); //この関数は2回実行されました showMessage(); //この関数は3回実行されました /* クロージャーの効果によりshowMessage()を呼び出しても変数countがリセットされない! それに加えて変数カウントが隠蔽されている為、他の関数から操作される心配がない! 外部変数に依存しない為、移植もgetFunctionShowMessage()をまるごとコピペするだけでOK! */
見事に成功です。
少々複雑になりましたがやっていることは難しくありません。
今回は以下の内容を実行するgetFunctionShowMessage()を追加しました。
- 変数countの定義(showMessage()内部に定義しているわけではないのでshowMessage()を呼び出してもリセットされない!)
- showMessage()の定義
- showMessage()の参照をreturnする(showMessage()の実行結果ではなく参照を返す)
変数countがgetFunctionShowMessage()内部に隠蔽された為、他の関数から操作される危険性はなくなりました。
移植の際もgetFunctionShowMessage()をまるごとコピペすればOKなので再利用が容易です。このように関数外部の変数や関数に極力依存しない作りにすることは非常に大切なことです。良いライブラリは他のプログラムに悪影響を与えません。
また、getFunctionShowMessage()を複数回呼び出したらどうなるでしょうか?変数countはリセットされてしまうのでしょうか?
var showMessage = getFunctionShowMessage(); var showMessage2 = getFunctionShowMessage(); showMessage(); //この関数は1回実行されました showMessage(); //この関数は2回実行されました showMessage2(); //この関数は1回実行されました showMessage(); //この関数は3回実行されました showMessage2(); //この関数は2回実行されました alert(showMessage == showMessage2) //false
getFunctionShowMessage()を複数回呼び出した場合返されるshowMessage()はそれぞれ独立したオブジェクトのようです。よって互いが影響することはありません。
再利用性の高いプログラムを書くための手法は他にも色々ありますが、クロージャーを使ったパターンが一番とっつき易いかと思いますので試してみてはいかがでしょうか。