Java プログラマから見た JavaScript
Java プログラマが理解しにくい JavaScript のポイント
Java プログラマが JavaScript を理解する上で重要だと思っているポイントが2点ある。
- this の理解
と
- クロージャの理解
だ。
ちょうど id:amachang さんのとこでその両方にかかるエントリーがあったのでそれを流用して、Java プログラマの立場から解説したい。
引用したコードを Java っぽく書いてみる
[javascript]XMLHttpRequest の onreadystatechange の this
http://d.hatena.ne.jp/amachang/20060921/1158796639
で動かなかったとされるコードは以下。(要点を絞り込むため、簡略化している)
var req = new XMLHttpRequest(); req.onreadystatechange = function() { switch (this.readyState) { : } };
XMLHttpRequest req = new XMLHttpRequest(); req.addReadyStateChangeListener( new ReadyStateChangeListener() { public void stateChangd(ReadyStateChangeEvent e){ switch (this.readyState) { : } });
細かい説明は省略するが readyState が XMLHttpRequest のフィールドだとすると、Java プログラマならこのコードに問題があることがわかるだろう。
それから期待通りに動作したとされるコードも Java っぽく書いてみる。
XMLHttpRequest req = new XMLHttpRequest(); req.addReadyStateChangeListener( new ReadyStateChangeListener() { public void stateChangd(ReadyStateChangeEvent e){ switch (req.readyState) { : } });
こんどは何故 req にアクセスできるのかわからないのではないだろうか?*2
JavaScript における this は実行時に決まる
前述の Java コードでの this はもちろん自分自身、つまり「ReadyStateChangeListenerのインスタンス」を指す。readyState フィールドを持っているのは XMLHttpRequest のインスタンスなのだから、当然期待通りには動作しない(コンパイルできない)。元の JavaScript のコードも似たような理由で動作しない。
Java のインスタンスメソッドはそのインスタンスへの操作として定義され、this は別のインスタンスを指したりはしない。が、JavaScript の function は変数に代入、つまりあらゆるオブジェクトのメンバになり得るため、function を定義した時点では this が何を指すかは決まらず、実行時にきまる。だからその function がどのように実行されるか判断できない場合、this は使用すべきでない。
以下の例がわかりやすかったので引用する。
function hoge(){ alert(this); } var myObj = new Object(); myObj.hoge = hoge; hoge(); myObj.hoge();
うん、わかりやすい。そして、
どうも、Javaとかやってる人にはそれがスゴイ気持ち悪いらしいのです。
うん、気持ち悪いw
クロージャはクロージャが生成された個所のローカル変数を参照できる*3
前述の this を req に書き換えた Java コードもコンパイルはできない。インナークラスからそのインナークラスが宣言されたスコープのローカル変数にアクセスできないからだ。
が、JavaScript のクロージャからはアクセスできるから意図したとおりに動作する。*4
Java でもローカル変数にアクセスすることは可能で、以下のように final 宣言をしてその変数が指す参照が書き換えられないことを保証する必要がある。
final XMLHttpRequest req = new XMLHttpRequest(); req.addReadyStateChangeListener( new ReadyStateChangeListener() { public void stateChangd(ReadyStateChangeEvent e){ switch (req.readyState) { : } });
クロージャでもローカル変数を書き換えてはならない
このエントリで一番言いたかったこと。
Java のインナークラスからローカル変数にアクセスする場合は、言語仕様上ローカル変数が書き換えられないわけだが、JavaScript のクロージャでもアクセスするローカル変数を書き換えてはならない下に追記あり。このことは以下のページに詳しい。
クロージャとOOPとJavaScriptの謎仕様
http://ishikawa.arielworks.com/memo/2005/07/24/031449
Java のインナークラスと関連付けて考えると、意外とすんなり理解できるかもしれない。
元記事の JavaScript コードも、以下のようにすると動作しない。
var req = new XMLHttpRequest(); req.onreadystatechange = function() { switch (req.readyState) { : } }; req.open('GET', '.'); req.send(null); req=null;
- -
(2007/02/17 追記)
さすがに4ヶ月間ほとんど JavaScript ばかりやっていると理解度が違うな。
さて、上の主張とは全く違うことを言うけど、アクセスするローカル変数を書き換えてはならないというのは間違いです。つーか書き換えられるのがクロージャのキモだった。ゴメン。
クロージャーを利用することでアクセスするローカル変数をプライベートに扱うことができます。これは以下に詳しいです。
Collection & Copy - JavaScriptにおけるプライベートメンバ
http://d.hatena.ne.jp/brazil/20051028/1130468761
ただ↓のまとめに書いていることは間違いありません。変数を"何処に"宣言して"何処から"アクセスしているか、ちゃんと理解してコードを書くようにしましょう。