ドラッグコードでいちいち (add|remove)EventListener する必要はありません。フラグ管理だけで十分です。
ただし、No.2 へのコメントを見る限り、ご質問のコードはメモリリークパターンである可能性が高いので、ページ破棄時の removeEventListener は必須となります。
var f = (function(that) { return function(e) { that.myFunc(); }; })(this);
elm.addEventListener("click", f, false);
のようにし、この f をどこかに保持しておいて下さい(cf. No.1、No.3)。可能であればメモリリークパターンを避け、全体として No.9 のように書くべきです。そうすれば、そもそも removeEventListener は不要です。下記の例も、それを前提に書いています。
オブジェクト名を接頭辞代わりに使ったり、prototype をメソッド置き場のように使うのは、JavaScript のオブジェクト指向とは無関係です。
this 値がズレて困るというのは、本来異なるデータを一緒くたにしている可能性があります。そこで、No.8 の後半では、イベントを監視する Listener と、イベントを処理する Handler の 2 つのデータを分離し、両者の間で this 値の調整を行いました。これらは本来、this 値を共有しない異なるオブジェクトだと判断したからです。
var HogeDragEvent = new function() {
//
function DragEvent() { ; }
(function() {
this.startDrag = function(e) { /* mousedown */; };
this.moveDrag = function(e) { /* mousemove */; };
this.stopDrag = function(e) { /* mouseup*/; };
}).call(DragEvent.prototype);
//
function HogeDragEvent() { DragEvent.apply(this, arguments); }
HogeDragEvent.prototype = new DragEvent;
HogeDragEvent.prototype.constructor = HogeDragEvent;
(function() {
this.moveDrag = function(e) { /* override */; };
}).call(HogeDragEvent.prototype);
HogeDragEvent.ready = function() { return new HogeDragEventListener; };
//
function HogeDragEventListener() {
var f = function(e) { return h[e.type](e); };
var h = new HogeDragEventHandlers(new HogeDragEvent, f);
return f;
};
//
function HogeDragEventHandlers(drag, caller) {
this.drag = drag;
this.caller = caller;
}
HogeDragEventHandlers.prototype = {
mousedown: function(e) { return this.drag.startDrag(e); },
mousemove: function(e) { return this.drag.moveDrag(e); },
mouseup: function(e) { return this.drag.stopDrag(e); },
unload: function(e) {
var d = document;
var w = d.defaultView;
d.removeEventListener('mousedown', this.caller, false);
d.removeEventListener('mousemove', this.caller, false);
d.removeEventListener('mouseup', this.caller, false);
w.removeEventListener('unload', this.caller, false);
},
ready: function(e) {
var d = document;
var w = d.defaultView;
d.addEventListener('mousedown', this.caller, false);
d.addEventListener('mousemove', this.caller, false);
d.addEventListener('mouseup', this.caller, false);
w.addEventListener('unload', this.caller, false);
}
};
this.ready = HogeDragEvent.ready;
};
HogeDragEvent.ready();
HogeDragEvent.ready();
かなりイイカゲンですが、ここでは 4 種類のインタフェース(もどき)を定義しました。つまり、少なくとも 4 種類の異なるオブジェクトクラスが存在すると、私が判断したということです。また、名前の衝突を避けるために全体を関数の中に入れ、外部公開する API は HogeDragEvent.ready だけにしています。
とは言え、複数のドラッグイベントが同時に生じるケースは少なく(マルチデバイス対応を目指すなら別ですが)、大抵はイベントリスナ 1 つあれば事足ります。であれば、上記の HogeDragEventHandlers と HogeDragEventListener はコンストラクタである必要がありません。極端な話、
var HogeDragEvent = { };
HogeDragEvent.Listener = function(e) {
return HogeDragEvent.Handler[e.type](e);
};
HogeDragEvent.Handler = {
startDrag: function(e) { ...; },
moveDrag: function(e) { ...; },
stopDrag: function(e) { ...; },
mousedown: function(e) { ...; },
mousemove: function(e) { ...; },
mouseup: function(e) { ...; },
ready: function(e) { ...; }
};
これで十分という可能性もあります。
しかし、コンストラクタなしでは、万が一、マルチデバイス対応が必要になったときに全部書き直しになるのでしょうか。必ずしもそうではありません。ECMAScript5 の Object.create を使えば任意の Object をプロトタイプとする Object を生成できます(Object.create は ECMAScript3 の範囲内でもある程度は実装できます)。
var createListener = function(handler) {
return function(e) {
return handler[e.type](e);
};
};
var myHandler1 = Object.create(HogeDragEvent.Handler);
var myHandler2 = Object.create(HogeDragEvent.Handler);
var myListener1 = createListener(myHandler1);
var myListener2 = createListener(myHandler2);
実のところ、コンストラクタも new も、JavaScript ではさほど重要ではありません。それらは少しだけ手間を減らしてくれるというだけです。「Object さえあれば良い」のが、JavaScript におけるオブジェクト指向です。
繰り返しますが、this 値がズレて困るなら、それは本来別のオブジェクトに属するものを、無理矢理単一のオブジェクトに閉じ込めた結果かもしれません。一度、this 値に応じてメソッドを分類・整理してみて下さい。
お礼
たびたびご回答ありがとうございます。 あれからオライリーのGood Partsという本を読んで少しだけ理解を深めました。 私がトライしていたのは「グローバル変数を1つだけにする」ことと、「prototypeでメモリを節約する」ことですが、それぞれのサンプルを無理矢理接着させて両立させようとしていたことが、そもそもの間違いであったようです。(highslide.jsは前者のサンプルです) 仰る通り、グローバル変数の削減には色々と方法があるのですね。盲目的に1つのオブジェクトに閉じ込めようとしていました。 頂戴した情報は、よく参考にさせていただきます。 変な質問に繰り返しご回答頂きましてありがとうございました。