なぜJavaScriptのthisは難しいのか
Q. なんでみんなthisでこんなに苦しんでいるの?
答えは簡単で、場所によってthisが何を指すのかが変わるからです。
特にこの問題はjQueryで起きやすいです。
かなり稀ではありますが、TypeScriptを使ってもやらかす人がたまにいます。
例として、以下のようなコードを考えてみます。
$(function(){
console.log(this);// -(1)
$("input[type=checkbox]").each(function(){
console.log(this);// -(2)
$(this).parent().find("label").each(function(){
console.log(this); // -(3)
$(this).addClass("requied");
});
});
})
上記の(1)~(3)の3か所で、thisを指すものは実はすべて異なります。
場所 | thisの指すもの |
---|---|
(1) | window(グローバルオブジェクト) |
(2) | チェックボックス |
(3) | ラベル |
このように、ただでさえコールバック地獄でthisが多用されることで迷子になる(thisが想定したオブジェクトではない)ことがたびたび発生します。
関数名 is not undefined
という単語を何度見たことでしょう。
JSerは、thisで挫折してはじめて一人前になるのです。
なぜthisがころころ変わるのか
諸説ありますが、JavaScriptのクラス概念は割とあいまいで、ES2015ではじめてクラス構文が制定されました。
実際に、昔のJavaScriptのクラスの作り方を見てみましょう。
(今でも、IE11対応しようとするとこんな書き方になります)
function HogeClass(name, age) {
this.name = name;
this.age = age;
}
HogeClass.prototype.hello = function(){
console.log("Hello, " + this.name);
}
さて、お判りでしょうか。
JavaScriptでは、クラス構文もfunction(関数)で定義していたのです。
つまり、JavaScriptではfunctionは関数と言いつつ、クラスとしても働くしメソッドとしても働くのです。
次に、上のクラス定義からインスタンス生成をします。
var hoge = new HogeClass('Taro', 18);
これだけみると、JavaやC#といったプログラミング言語とそう変わりませんね。
しかしながら、クラス定義もfunctionで定義する以上、オブジェクト指向言語でいうところの「インスタンス」と「メソッド」が同じ記載となってしまいます。
ES2015が呼び起こす悪夢
さて、QiitaでJavaScriptを読んでいる皆様はIE11なんて捨ててしまいたい方々でしょうから、ES2015の記載方法で書きまくっていると思います。
あなたは、以下のJavaScriptコードを書こうとしています。
ヘッダの".enable-item"のクラスのついている要素を、マウスオーバー時に赤くする
HTMLは以下のようになります。
この場合、メニュー4だけはマウスオーバーしても赤くしたくないことになります。
<header>
<a href="#" class="enable-item">メニュー1</a>
<a href="#" class="enable-item">メニュー2</a>
<a href="#" class="enable-item">メニュー3</a>
<a href="#" >メニュー4</a>
</header>
これをjQueryで書こうとするとこうなります。
$(function() {
$('header').find('.enable-item')
.on('mouseover', function() {
console.log('over.');
$(this).css('color', 'red');
})
.on('mouseleave', function() {
$(this).css('color', 'blue');
});
});
See the Pen qiita-jquery-link1 by hikaruright (@hikaruright) on CodePen.
さて、ES2015を使ってコードを書き換えて、以下のように記載してしまいました。
この際、どのようなことが起きるでしょうか。
$(() => {
$('header').find('.enable-item')
.on('mouseover', () => {
console.log(this);
$(this).css('color', 'red');
})
.on('mouseleave', () => {
$(this).css('color', 'blue');
});
});
実際に触ってみると、色が全く変わっていません。
See the Pen qiita-jquery-link2 by hikaruright (@hikaruright) on CodePen.
ここで何が起きたかというと、
アロー関数に書き換えたことでthisが迷子になった
のです。
従来のfunctionでは、内部的にオブジェクトとして定義されていましたが、
アロー関数はその関数内にthisをバインドしません。
そのため、
「新しい記法に統一するため、一括でfunction(){}
を()=>{}
に書き換えます」
ということが起きた場合には注意が必要です。
というか、間違いなく何か起きます。
もしこの先あなたが開発に携わった際にそんな言葉を聞いた場合には、
上記のことを思い出して行動していただけると幸いです。
さいごに
JavaScriptほど、thisのことがよく分からなくなる言語はないのではないでしょうか。
thisのことは嫌いになっても、JavaScriptのことは嫌いにならないでくれると嬉しいです。