この記事について
この記事の目的
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());
って書けば内部的にadd()が呼ばれる、っていう風にした方が良くない?って思うんですね。
で、実は(ここが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
」はなくなりました。
なくなったからなんなのさ、っていうのが私のような古い人間の感覚なんですが、JavaScriptの世界ではこういうことをけっこう普通にやるんですよ。
「関数自体を返値として返せる」って言われれば、へーそうなの、と理解したようなつもりにはなります。
ですけど、こんな風に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のクロージャのソースが、いつのまにかできちゃいましたよ!
なんだー、そういうことだったのね。
関数を値として扱ったり、無名関数を使ったりすると、こういう「いかにもJavaScript!」ってカンジのソースになるのか。わかったわかった。
なるほどね~。
念のため実行結果も書いておきますね。以下の通りです。
1
2
1
2
途中のまとめ
ここまでの内容を簡単にまとめます。
- クロージャってのは、Javaでいうところの「メソッドが1つだけのクラス」みたいな仕組みである
- 概念的には超簡単
- そんなわけで、独立したインスタンスを複数作ることができる
- 妙に難しく見えたのは、JavaScript特有の、関数自体を返したり、無名関数使ったりという、クロージャとは全然関係ない部分のせい
- もう一回言うけど、クロージャの概念自体は全然難しくない、っていうか、メチャメチャ簡単。
で、結局クロージャの何がうれしいの?
クロージャ説明してるいろんなブログとか見てても、結局ここに行き着きますよね。
でもね、多分意味合いが違うんですよ。そういうブログとこの記事では。
多分、JavaScriptからプログラミングに入った人たちっていうのは、(広義の)「クラスとインスタンス」っていう考え方に触れるのが、プログラミングを始めてけっこう時間がたってからだと思うんですよね。なぜなら、JavaScriptの世界では、そんなこと知らなくても現場で通用するくらいのコードはかなり書けてしまうから。
そういう人たちがクロージャでつまずくのは、多分「クロージャは関数のインスタンスだ」ということを理解するまでに、すごーく高い壁があるからだと思うんです。まず、「インスタンスってなに?」「初めて聞く言葉だけど?」ってところからスタートですからね。
それに、クラス-インスタンスの利点がわからなかったら、クロージャの利点はわからない。
世の中にたくさんある**「クロージャの何が嬉しいのか」っていう疑問は、ようは「それはクラスベースオプジェクト指向を理解すればわかる」**というのが答えになるんではないか、と個人的には思ってます。
反面、Javaからプログラミングに入った人は、もう最初っからnewじゃないですか。クラスがあって、newして、インスタンスができて、やっと使える。これがクラスベースのオブジェクト指向言語のJavaの構造ですから、そこが理解できないとJavaのコードはまったく書けない。
だから逆に、Javaがわかる人なら、クロージャはすぐにわかる。「クロージャは、メソッドが1つだけのクラスなんですよ」という切り口で説明する人が、今まであまりいなかった、ってだけなんじゃないかと思います。
で、改めて。
クロージャの何が嬉しいのか。
ここまで書いておいてアレですが、実はJava屋さんでもこの疑問は持ったりするんじゃないかと思います。
ただ、それは従来言われ続けてきたクロージャに対する疑問ではなく「クロージャなんかじゃなくて、クラスでいいじゃん?」っていう意味で、ですけど。
実際、ここまでの内容からすると、「いや、だったらクロージャじゃなくて、普通にクラス使えばいいじゃん?」って思いません?
実際にクロージャは、劣化版の(つまり、メソッドが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ってけっこう面白いと思いません? これはこれで深い世界だと思います。
SPA(Single-page Application)とかだと、今までのようにWebページは画面遷移するたびに使い捨てるのではなく、ずっと表示し続けられ、状態を持つようになります。
そうなると、従来のJavaScriptプログラミングとはまったく異なる、むしろサーバーサイドプログラミングに近いセンスが必要とされるんじゃないかと思います。クラスやオブジェクトもわんさと書かないといけない。
Javaで培ってきた感覚が、けっこう必要とされるのでは? なーんて思ってます。
この機会にJavaScriptを本格的に勉強し始めても面白いんじゃないでしょうかね?