空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

JavaScript のクラスのメンバ関数をイベントハンドラに登録する方法

先週の土曜日JavaScript をいじり始めて真っ先に戸惑ったのが C++JavaScript の間の this の違いだった。JavaScript には「カレントオブジェクト」なる概念があって,this は「カレントオブジェクトへの参照」という定義らしい。で「カレントオブジェクト」そのものの定義はよく調べていない。そんなわけでグズグズなんだけど,いろいろ観察してみて分かったのは,どうやら

function clickHandler()
{
    alert(this.id);
}

な関数があって,

<span id="hoehoe" onclick="clickHandler()">ほえほえ</span>
<span id="hoihoi" onclick="clickHandler()">ホイホイ</span>

で,呼び出されるとき「ほえほえ」をクリックしたときには<span id="hoehoe">(span ほえほえ)がカレントオブジェクトになり,clickHandler() の this は span ほえほえへの参照になる。で「ホイホイ」をクリックしたときは,this は今度は <span id="hoihoi">(span ホイホイ)への参照になっているらしいということ。この振る舞いは,JavaScript で作ったクラスのメンバ関数をイベントハンドラに登録(少なくとも,そういうコンセプトを実現)しようとすると問題になる。この前は

var ClassA = Class.create(); ClassA.prototype =
{
    x : null,

    initialize : function(x)
    {
        this.x = x;
            :
            :  // element が <span id="hoehoe"> を参照するように設定
            :
        addEvent(element, "click", this.clickHandler); // element の onclick に this.clickHandler() を登録
    },

    clickHandler : function()
    {
        alert(this.x);
    }
};

var a = new ClassA(10);

という風にイベントを登録したら,span ほえほえをクリックして ClassA::clickHandler() が呼び出されたとき,関数中の this が span ほえほえへの参照になっていて,「x というメンバはありません」というエラーになって困った。私が意図したのは をクリックしたときに,a.clickHandler() が呼ばれるような振る舞いだった。

これを実現するために私は「this が指す『カレントオブジェクト』は呼び出し時の文脈によって変わりうるから,それを文脈によって変わらないような呼び出し方にすればよい」と考えて対処法を考案してみた。

対策は以下の通り。ClassA::clickHandler() があったら,それに対応するグローバル関数

function classa_clickHandler(_this_) {_this_.clickHandler();}

を作って

addEvent(element, "click", this.clickHandler);

するところを

var _this_ = this;
addEvent(element, "click", function() { classa_clickHandler(_this_); });

という風に変える。this を代入して作った _this_ は値なので,いつどのような形で呼ばれても,不変だろうというわけ。このアプローチはうまく動いた。しかし,「余計な関数を作るのは嫌だなぁ」とおもって微妙に違う次の形

var _this_ = this;
addEvent(element, "click", function() { _this_.clickHandler(); });

を試したら,エラーになった。何でエラーになるのかは今のところ分からないんだけど。最初の書き方は _this_ が生き続けられるけど,次のはダメってことなんだろうな。
とりあえず余計な関数を一つ追加する(通常一行程度)だけで目的は達せられたので,まあいいかな。

まとめ

  • JavaScript クラスのメンバ関数をイベントハンドラで呼ぶ方法について考察
  • メンバ関数とは別に,_this_ を引数にとるようなグローバル関数を用意して,それをイベントハンドラに登録する方法で回避
  • classa_clickHandler(_this_) 方式は OK だけど _this_.clickHandler() 方式は動かない。今のところ,xoinu には説明不可能。まあいいや(笑)

箇条書きでまとめてみました。