この記事について
この記事の目的
JavaScriptのクロージャという技術を、従来と異なる切り口で解説したいと思っています。
説明にはJavaを使用していますので、Javaの基本的な(つまり、クラスの定義とnewできるくらいの)知識が必要です。
対象者
- クロージャの説明をいろいろ読んだり聞いたりしたけど、今ひとつピンときていない。あるいはまったく理解できていないJavaプログラマー。
クロージャとは
言いますよ? いいですか?
クロージャとは、、、、
メソッドが1つしかないクラス(みたいなモン)である
あらまー。言っちゃった。
言っちゃった以上は説明します。
例:カウンター
早速ですけど例題から見ていきたいと思います。
クロージャの説明には、よく「カウンターを作ろう!」的な例があります。これは例として間違っていないと思いますので、ここでもカウンターを例にして説明します。
以下がクロージャで実装したカウンターの例です!
// クロージャを作る
function createCounter() { // カウンターを作るクロージャ
var count=0; // カウンターの値を格納する変数
return function() { // 関数の中に関数宣言
return ++count; // カウンターの値を増やしてから返す
};
};
なるほどねー。
わからないねー。
JavaScriptってJavaと比べると文法が似てるんだけど違うんだよねー。。。
わからないまま、使い方を見てみます。
// クロージャを使う
var cntA=createCounter(); // カウンターAの作成
console.log(cntA()); // まず1が表示される
console.log(cntA()); // 次に2が表示される
console.log(cntA()); // 次に3が表示される
var cntB=createCounter(); // カウンターBの作成
console.log(cntB()); // まず1が表示される
console.log(cntB()); // 次に2が表示される
console.log(cntB()); // 次に3が表示される
[結果]
1
2
3
1
2
3
ふーん。。。
……やっぱクロージャって意味わからんね。
と思いますよね。
では気を取り直して、これをJavaで書き直してみます。
そうしたらすっげー簡単ですよ。「え? たったこれだけのことなの?」って思いますから、マジで。
Javaでの例
じゃあ、まったく同じモノ(つまりカウンタークラス)をJavaで作ってみます。
ではいきますよ? Javaの例がコレだ!
class Counter // カウンタークラス
{
private int count; // カウンターの値が入っているフィールド
public int add() // 加算するメソッド
{
count ++; // カウンター値を足してる
return count; // そして返してる
}
}
Javaを使える方から見たら、どっからどう見てもカウンターです。クラス作成の基本中の基本。難しいところ一切なし。死ぬほどわかりやすいです。
次は使い方です。
インスタンスを2つ(cntA
,cntB
)作って、add()
メソッドを呼んで、値を増やします。
当然のことながら、2つのインスタンスは独立しているので、お互い別々に値が増えます。
ソースはこんな感じ。
// 使うとき
Counter cntA=new Counter(); // インスタンスcntAを作成
System.out.println(cntA.add()); // 1が表示される
System.out.println(cntA.add()); // 2が表示される
Counter cntB=new Counter(); // インスタンスcntBを作成
System.out.println(cntB.add()); // 1が表示される
System.out.println(cntB.add()); // 2が表示される
[結果]
1
2
1
2
いやー、あったり前だよね。簡単簡単。誰でもわかるわ、こんなモン。
これですよ。
これがクロージャなんです。
……え?!
マジ?! たったこれだけのこと?!
そう。
たったこれだけのことなんです。
要するに、(もう1回言いますけど)クロージャというのはメソッドが一個だけのクラス(とそのメソッド)のことなんですなー。
たかだかこんな程度のモノに、「クロージャ」なんてたいそうな名前をつけて特別扱い。JavaScriptの世界は理解できないですなー。1
これがわかれば、「クロージャの特徴と言われている、わかるようなわからないようなもろもろ」も、Java的にスッキリ理解できるようになります。
クロージャからは関数の外側の変数にアクセスできる!
いや、それってそもそもクロージャの特徴でもなんでもなくてさ。関数が外側のスコープにある変数にアクセスできるのは当たり前だから。クロージャは関数の中に関数がある!
JavaScriptでは、classキーワードが(最近まで)なくて、代わりにfunction()を使うからね。その中でメソッド(これもfunction())を定義すれば、関数の中に関数があるってことになるよね。クロージャは状態を持てる!
Java的には、インスタンスが生きている間はインスタンス変数が生きているのは当然だしね。取り立てて騒ぐことではないね。クロージャを使うとグローバル変数を減らすことができる!
Java的にはそもそもグロバール変数なんて概念自体がないからね。Java的には、なくて当たり前のモノ。元々グローバル変数なんてガバガバ使ってること自体がちょっとどうかと思うんだよね。
なんかなぁ、、、。わざわざ特徴とか言うほどのモンじゃないんじゃ? と思ったり思わなかったり。
たったこれだけのことだったんですねぇ。。。。
というわけで、クロージャの概念はここまででカンペキに理解できたと思います。
え? カンペキ? これで?
はい。概念的にはホントにこれだけのことですから。
では概念が理解できたところで、もう一度JavaScriptで書かれたクロージャのソースを見てみましょう!
function createCounter() { // カウンターを作るクロージャ
var count=0; // カウンターの値を格納する変数
return function() { // 関数の中に関数宣言
return ++count; // カウンターの値を増やしてから返す
};
};
var cntA=createCounter();
console.log(cntA());
console.log(cntA());
うーん。
さっきよりはちょっとはわかる気もしますけど、でもやっぱりわからないな。。。
と思いますよね。
これはなぜか。
これは、クロージャの概念が難しいからではなく、JavaScript独特の記法の問題です。クロージャは概念的にはJavaのクラスと同じようなモノなんですが、上記のソースはJavaではおなじみではない、JavaScript独特の以下のような特徴をフルに使っています。
- 関数を値として扱える
- ので、変数に代入したり、返値として返したりすることができる
- 関数に名前がなくてもよろしい
だからなんか実際以上に難しく見えるんですね。
ここもJavaプログラマには敷居が高い部分なので、ちょっと見てみましょう。
Javascriptで書き直してみる
では、先ほど見たJavaの例を、Java的記法をそのままに「忠実に」JavaScriptに変換した例を挙げましょう。
function counter() // クラスをfunction()に変更
{
var count=0; // カウンターの値が入っているフィールド
this.add=function() // 加算するメソッド
{
count ++; // カウンター値を足してる
return count; // そして返してる
}
}
// 使うとき
var cntA=new counter(); // クロージャのインスタンスcntA作成
console.log(cntA.add()); // 足して、返値を画面出力
console.log(cntA.add());
var cntB=new counter(); // クロージャのインスタンスcntB作成
console.log(cntB.add()); // 足して、返値を画面出力
console.log(cntB.add());
[結果]
1
2
1
2
おー。これならJavaの脳でも理解できる。実にJava的なJavaScriptです。
実行結果も問題ありません。
で。
この記法はJavaプログラマにはわかりやすいんだけど、その反面、当然ながらちょっとJavaScriptっぽくない。
例えば、console.log(cntA.add());
って書き方は冗長な感じがするんですよ。
というのは、ここでcounter()
はクラス的な役割をしているわけですけど、見ての通り実際にはfunction()なので、そもそもは関数なんです。
ということになると、どうせメソッドは一個しかないんだし、console.log(cntA());
って書けた方が良くない?って思うんですね。
(ここがJava屋さんにはおなじみではない部分なんですが)JavaScriptでは、関数はオブジェクトです。つまり、関数自体を値として変数に代入したり、返値として返したりできます。
ということになると、counter()
を「add()
関数自体を返す関数」として実装すればいいじゃない! っていうのがJavaScript的な発想、ということに(多分)なります。
関数を値として扱ってみる
では関数を値として扱う方法を見てみましょう。
と言っても、これはそんな身構えるような話ではなく、実はけっこう簡単です。
関数の末尾のカッコを取ってあげれば(つまり、add()
だったらadd
と記述すれば)関数を値として扱うことができます。
一番簡単な例は、例えば以下のような感じだと思います。
function hello() // 関数の定義
{
console.log("hello world!"); // 文字列の出力
}
var hoge=hello; // カッコを取って、変数に代入
hoge(); // 変数のあとにカッコをつけると、代入されている関数が実行できる
var fuga=hoge; // さらに別の変数に代入
fuga(); // ここでもカッコをつけると、代入されている関数が実行できる
実行結果
hello world!
hello world!
おおおお。関数が値として変数に代入されている!
変数のあとにカッコをつけると関数として実行できる、っていうのはなかなかかっこいい仕組みですね。
では、これをカウンターの例に応用してみましょう。
counter()
を呼んだ時に、add()
関数それ自体が返ってくるように変更することができます。
function counter() // add()関数自体を返すようにする
{
var count=0;
this.add=function()
{
count ++;
return count; // カウンターの値を返す
}
return add; // ★★★★add()関数「自体」を返している
}
// 使うとき
var cntA=counter(); // add()が返ってきて、cntAにadd()が入っている
console.log(cntA()); // add()の実行結果が表示される
console.log(cntA());
var cntB=counter(); // add()が返ってくる
console.log(cntB()); // add()の実行結果が表示される
console.log(cntB());
どうでしょう。なかなかJavaScriptらしくなってまいりました!
無名関数にチャレンジ
とはいえ、実はもっとJavaScriptらしくできます。
メソッドとして用意されているadd()
ですが、これはcounter()
の中からしか呼べません。だったらわざわざadd
とかって名前もつけなくていいんじゃない? っていうのがJavaScript的な発想です。つまり、無名関数ですね。
これは、部屋に自分以外の人が一人しかいないときには、わざわざ名前を呼ばずとも、「ねえ」とか言えば相手が振り向くのと同じ(かな?)。
余談ですけど、Javaにも無名クラスとかあるにはありますけど、そういうのってあんまり使うとね……というのがJavaの世界には空気としてあります。「無名クラスなんか使うと可読性が下がる」ということもあって、ちょっとマニアがドヤ顔で使うモノ、というイメージ(個人の感想です)。
でもJavaScriptの世界では、割とおおらかに無名関数が使われます。サーバーサイドとフロントエンドの文化の違いでしょうか。
まあそれはいいとして。無名関数を使ってみましょう。
変更する部分は、具体的には以下です。
this.add=function()
{
count ++;
return count; // カウンターの値を返す
}
return add; // ★★★★add()関数「自体」を返している
無名関数を使うと、上記のソースを以下のように書き換えることができます。
return function() // ★★★★ return の直後に、返すべき関数を記述する
{
count ++;
return count; // カウンターの値を返す
}
スゴいですねー。たしかに「add
」はなくなりました。
「関数自体を返値として返せる」って言われれば、あーそうなの、と理解したようなつもりにはなります。
ですけど、こんな風にreturn
文の直後に、実際に返す関数をその場で定義されると、Java屋さんとしてはさすがに違和感を感じざるを得ません。
これはおそらく
var a=5;
return a;
なんて書くくらいなら
return 5;
って書いた方が早いじゃん、っていう感覚なんでしょうかね?
もう一度クロージャのソースを見てみよう
では、無名関数を反映した全体のソースです。
function counter()
{
var count=0;
return function() // ★★★★ return の直後に、返すべき無名関数を記述した
{
count ++;
return count; // カウンターの値を返す
}
}
使うとき
var cntA=counter(); // 無名関数自体が返ってきて、それを変数cntAに代入している
console.log(cntA()); // cntAという名前がついたから呼べるよ!
console.log(cntA());
var cntB=counter(); // 別の変数に代入すれば、別インスタンスになる
console.log(cntB()); // 独立して動作するよ
console.log(cntB());
どうでしょう。
冒頭で「何やってるんだか全然わかんねー」って言っていた、JavaScriptのクロージャのソース、そのままです。
なんだー、そういうことだったのね。わかったわかった。
なるほどねー。。。。
念のため実行結果も書いておきます。以下の通りです。
1
2
1
2
途中のまとめ
ここまでの内容を簡単にまとめます。
- クロージャってのは、Javaでいうところの「メソッドが1つだけのクラス」みたいな仕組みである
- 概念的には超簡単
- そんなわけで、独立したインスタンスを複数作ることができる
- 妙に難しく見えたのは、JavaScript特有の、関数自体を返したり、無名関数使ったりという、クロージャとは全然関係ない部分のせい
- もう一回言うけど、クロージャの概念自体は全然難しくない、っていうか、メチャメチャ簡単。
で、結局クロージャの何がうれしいの?
結局ここに行き着きますよね。
ここまでの内容からすると、「いや、だったらクロージャじゃなくて、普通にクラス使えばいいじゃん?」って思いません?
実際にクロージャは、劣化版の(つまり、メソッドが1つだけという制限がある)クラス-インスタンスそのものです。2
それをなんか特別に便利な難しい仕組みみたいに扱っちゃったりして、全然意味分かんねー。みんなが難しい難しいっていうからどんだけ難しいのかと思ったら、超簡単じゃねーか。
というのが一般的なJavaプログラマの皆さんの感想だと思います。
実際に、クロージャの利点(と言われている)もろもろは、クラスで実装しても、ぜーんぶ解決できます。
さらにいえば、クラスで実装すればメソッドだって複数持てます。
今回はカウンターの例を出しましたけど、普通に考えてカウンターを作るなら数を増やすだけではなくて、カウンターのリセットや、場合によっては減らしていくという機能も欲しくなったりするかもしれない。でもダメ。クロージャはメソッド1つしか持てないからね。
そんなんだったら、クロージャじゃなくてちゃんと最初からクラス使った方が良くない? って思う人は多いんじゃないでしょうか?
これはですねー。
個人の印象ですが、これは多分、価値観の違いでしょうか。
Javaの世界では、クラス作るのが当たり前ですよね。というか、選択肢がそれしかない。
でも、JavaScriptの世界では、クラス作ってもいいし、クロージャ作ってもいい。
だったら簡単な方で、と思うわけですが、「簡単」って主観ですからね。わざわざクラス作るのなんてめんどくさい、っていう感覚を持っている人はJavaScriptの世界にはけっこう多いのではないか、と思います。
あともう1つ言えるのは、JavaScriptの世界はやっぱり基本的にfunction()中心で回ってるんですよね。
例えば、そもそもJavaScriptって「あるボタンが押されたときに実行されるコールバック処理を記述するためのもの」って当たりが起源だと思うのです。
最近はこういう書き方は流行らないのであまり目にすることがなくなりましたけど、例えばボタンをクリックされたらJavaScriptのfunctionを呼んで、内容をチェックする、というシンプルな仕組みを実装しようとしたら、こんな書き方をしたものです。
<input type="button" name="submitButton" value="追加" onClick="checkInput();">
onClick="checkInput();"
の部分ですけど、これは「このボタンをクリックされたら、checkInput();
というJavaScriptの関数を呼ぶ」という意味です。
見ていただければ雰囲気はわかると思いますが、ここにはクラスだのインスタンスだのっていうのをずらずら記述するのはちょっとそぐわない。
また、最近だとこういう部分はjQueryを使ったりしますが、ここでコールバック関数を実装する場合にはこんな風に書きます。
$(function(){
var pushed=false; // まだ押されてない
$('#button').on('click', function() {
if(pushed===false)
{
pushed=true; // 押された!
submit(); // サブミット
} else { // 2回submitするのを防止している
alert('もう押しましたけど?');
}
})
})
こんなのも一種のクロージャなんですが、こういうフレームワークレベルですと、「いやいや、俺はクラスで実装したい!」なんてことは、そもそもまったく考慮されていません。
多分クラスで書けないことはないと思いますが、さてどう実装しよう? ってちょっと考えてしまう。
それだったらクロージャの方がお手軽かなー? と思います。要は慣れの問題? ですかね?
結論
というわけで、
- クロージャは実は簡単だった
- でもこれだったらクラスで実装した方がいいかも、と思うこともある
- とはいえ、Webページ上で動かすにはクロージャの方が相性がいいかも
というところでしょうか。
いかがなものでしょう?
これでJavaプログラマーの皆さんがクロージャを理解できたら幸いです。
あと、JavaScriptってけっこう面白いと思いません? これはこれで深い世界だと思います。