2015/04/02

【TypeScript】thisの使い方にハマった!thisを保持する3つの方法

photo by Brendio

C#erが、それほど勉強せずにTypeScriptに足を踏み入れたせいで、thisの使い方にハマってしまった。

JavaScriptはちょっと勉強していたので、「thisは今の親オブジェクトを参照する」ということだけは知っていた。しかし、それだけでは認識が甘かったようだ。



サンプル用HTML



カウントアップ、ダウンのボタンを押すと、カウントが表示されるHTMLを元にTypeScript、JavaScriptのソースコードをまとめていく。


エラーになるケース


一見、どこもおかしくないように見える。
しかし、Counterクラスで使っている「this.count」が実行時に未定義になってしまう。

TypeScriptの場合
コンパイルで生成されたJavaScript
このように、クラスのメソッドをイベントから呼び出したり、コールバックとして使うと、元の「this」の値が失われてしまう。

具体的には、上記の例でいうとcounter.countUpを呼び出したときに、thisの値が「button#count-up」に変わってしまう。
もちろん「button#count-up」なんかに「count」という変数は定義されていないので、未定義エラーになる。



対策1:アロー関数


メソッドをプロパティに置き換え、アロー関数を使って初期化する方法。


TypeScriptの場合
コンパイルで生成されたJavaScript
コンパイルされたJavaScriptを見ればすぐわかると思うが、thisを「_this」に退避している。
これにより、イベントやコールバックで使われても、Counter.countを参照することができる。


注意点


メソッドをプロパティとして保持するため、インスタンスごとに複製されてしまう。
もしインスタンスが大量に作成される場合は、パフォーマンスに要注意。


対策2:クロージャ


インスタンスメソッドをfunctionで囲み、クロージャにする方法。

TypeScriptの場合
コンパイルで生成されたJavaScript
ネットで調べてこの方法にたどり付いたけど、ウチが知ってるクロージャと違う!
まだ勉強不足なので、functionで囲むと、なぜthisの値が失われないかよくわかっていない。



追記:2015/04/02 21:30
わからないまま放置するのは危険なので調べてみた。

すべての関数に渡されるthisの値は、関数が実行時に呼び出される際のコンテクスト(状況)に依存します。
開眼!JavaScript p.86

イベントに登録されたメソッドのthisは、イベントの発生源のオブジェクト(ここでは「button#count-up」など)を参照してしまう。

ただ、thisがイベントの発生源を参照するのは、イベントハンドラ内(ここでは「onclick」)だけ。

言い換えると、イベントハンドラに直接メソッドを指定しなければ、thisは保持されるということ。

だから以下のように、「インスタンスメソッドを呼ぶ用のメソッド」を用意し、それをイベントハンドラに登録すれば、このthis問題が解決される。
var counter = new Counter();

function onclick_CountUp() {
    counter.CountUp();
}

document.getElementById('count-up').onclick = onclick_CountUp;

JavaScriptには便利な機能があって、もっと簡単に「インスタンスメソッドを呼ぶ用のメソッド」を簡単につくることができる。
それがクロージャ。(→ この対策2となる。)



対策3:bind関数


JavaScriptのbind関数を使う方法。

TypeScriptの場合
コンパイルで生成されたJavaScript
bind関数は、引数をthisキーワードに設定した新しい関数(束縛された関数)を生成する。
それにより、thisの値を束縛(保持)できる。

ただ、ちょっと長くなるのが難点かな。

注意点


bind関数はECMAScript5からサポートされているので、IE8などの古いブラウザでは動作しないので注意。



さいごに


stackoverflowとかいろいろ検索してみたが、ほとんどの場合「対策1:アロー関数」を使っている。
複数インスタンスを作らなければ、一番簡単だからかもしれない。

まだJavaScript・TypeScriptともに未熟なので、正直どれがよいのかわからない。
参考書を読んでも、どれを推奨しているというわけではない。
状況によって使い分ける必要がありそうだ。

もし「こうした方が良いよ」とかありましたら、コメントなりTwitterなりで教えて下さい。


以上

written by @bc_rikko

0 件のコメント :

コメントを投稿