白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

JavaScriptでメソッドをイベントバインドするとthisは何を指すの?

こんにちは。

ワールドカップが始まりましたね。
3時からとかだといっそ諦めがつくのですが、
0時くらいから始まる試合とかをつい見てしまって翌日ねむねむになる気がしてなりません。

さて、この前JavaScriptを書いていてふと思ったこと。
表題の通りです。

先に結論を書いてしまえば、イベントが発生したDOMになります。

JavaScriptのthisの指すものの種類はいろいろとあるのですが、
今回の目的はそれを解説することではないので、話題にする2つだけまず確認しましょう。

メソッドが指すthis

JavaScriptに限った話ではないですが、
わりとあいまいに使われている「関数」と「メソッド」は実際の挙動としては明確に区別されます。
Javaとかのオブジェクト指向言語だと関数はstaticメソッドとか呼ばれますね)

簡単にいえばメソッドとはオブジェクトに属する「メンバ関数」のことです。
なので、基本的にはオブジェクトに対する操作を定義した手続きなのですが、
オブジェクトのメンバにまったく関係しない、参照透過性のある関数を定義することができないわけではありません。

おっと、ちょっと話がそれましたね。
つまり、こんな感じのfunctionの定義はメソッドです。

var obj = {};
obj.method = function() {
    alert("hoge");
}

もしくはこんなのとか。

var obj = {
    method: function() {
        alert("hoge");
    }
};

で、このメソッドの中でthisが何を指すかというと、「自身が属しているオブジェクト」です。

var obj = {
    value: "hoge",
    method: function() {
        alert(this.value);
    }
};
obj.method();  // hoge

こんな感じですね。ほかの言語でも大体同じなんじゃないでしょうか。

イベントバインドした関数が指すthis

次にイベントバインドについてみていきましょう。

クライアントサイドのJavaScriptを考えた場合、画面初期表示時に様々な処理を行うほかに、
ユーザが「ある要素をクリックした」とか「マウスカーソルを動かした」とか「キーボードで何か入力した」
といった様々なイベントが発生します。

これらのイベントが発生した際に、なにかしらの処理をさせたい場合、
イベントに処理を「バインド」してあげる必要があります。

document.getElementById("hoge").addEventListener("click", function() {
    alert("hogeをクリックしたよ!");
});

jQuery的で書くとこんな感じ。

$("#hoge").on("click", function() {
    alert("hogeをクリックしたよ!");
});

で、この関数の中でthisが何を指すかというと、「イベントが発生したDOM」です。

<input type="text" id="hoge" value="fuga" />

document.getElementById("hoge").addEventListener("click", function() {
    alert(this.value);
});
// クリックするとfugaがアラートされる(inputクリックしたらアラートとかどういうこと)

メソッドをイベントバインドした場合のthis

やっと本題。

これら2つって実は共存することができます。
つまりどういうことかというと、

<input type="text" id="hoge" value="fuga" />

var obj = {
    value: "hoge",
    method: function() {
        alert(this.value);
    }
};
document.getElementById("hoge").addEventListener("click", obj.method);
// クリックするとfugaがアラートされる

この場合、「this.value」が指すのは

・「obj」のメンバ変数である「value」の値である「hoge
・イベントが発生したinputが持つ値「fuga」

どっちなのでしょうか。

正解は、イベントが発生したinputが持つ値「fuga」です。

じゃあこんな風にinputが持たない属性の「val」とかにしてみると使えるのかというと、
あくまでinputから「val」属性を探しに行って、結果は「undefined」になります。

var obj = {
    val: "hoge",
    method: function() {
        alert(this.val);
    }
};
document.getElementById("hoge").addEventListener("click", obj.method);
// クリックするとundefinedがアラートされる

オブジェクトのメンバを参照することはできないので残念ですね。
で終わってもいいのですが、せっかくなので回避方法を検討します。

var obj = {
    val: "hoge",
    method: function() {
        var self = this;
        return function() {
            alert(self.val);
        };
    }
};
document.getElementById("hoge").addEventListener("click", obj.method());
// クリックするとhogeがアラートされる

こんな感じでオブジェクトのthisを囲い込んだクロージャをバインドしたりするとなんとかなったりしますね。

使いどころがそんなにあるかは謎ですが、覚えておいて損はないかな。