イベントで受け取り側のメンバを使用するのにbind以外の方法

結局は単なるクロージャーなんですけど、メモリリークの罠があるので回避する方法を解説してみました
自分の学習用コードにちょっとコメント入れて張っただけですが。。。参考になれば幸いです

```javascript:test.js
/**
 * イベントハンドラのテスト
 *
 * コマンド
 * node test.js 5
 * 
 * 最後の引数でシナリオ番号を指定します
 *
 * シナリオ１　thisがpublisherになっている
 * シナリオ２　thisの束縛。解除不可能。メモリリークの発生
 * シナリオ３　thisの束縛。解除可能。メモリリークの発生
 * シナリオ４　thisの束縛。解除可能。メモリリークの回避
 * シナリオ５　実はクロージャーで簡単に解決できるんだけど
 *
 *	名称の確認
 *		publisher : イベントを発行するインスタンス
 *		subscriber: イベントを受け取るインスタンス
 *
 *	イベントを追加した際に起きるちょっとした問題を解決する
 *
 *	「「　イベントハンドラ内のthisがpublisherになってしまうのでsubscriberのメンバを
 *	　　　イベント発生時に使用できないよどうやって解決しましょう？　」」
 *
 *	安易に解決するとメモリリークにはまりやすい罠があったりしてハマってしまいます
 *	メモリリークを調査する為のコードは記述していないので
 *	process.memoryUsage()とglobal.gc()を適当に入れて確認してください
 *	--expose_gcを コマンドに追加しないとglobal.gc()は動作しません
 */

//コマンドライン引数からシナリオ選択（ここは単なるおまじない）
var maxScenarioNo = 5
  , scenarioNo = parseInt(process.argv[process.argv.length -1]) || 1;
if (scenarioNo < 1 || maxScenarioNo < scenarioNo) {
	scenarioNo = 1;
}

//使用モジュール
var events = require('events')
  , util = require('util');

/**
 * イベントを発火する担当クラス
 * @param  {String} name
 */
var Publisher = function (name) {
	this.name = name;
};
//イベントを発行出来るように継承
util.inherits(Publisher, events.EventEmitter);
//イベント発行用メソッド
Publisher.prototype.send = function(message) {
	this.emit('send', this.name, message);
};

/**
 * イベントを受け取る担当クラス
 * @param  {String} name
 */
var Subscriber = function (name) {
	this.name = name;
};
//イベントハンドラ
Subscriber.prototype.onSend = function (publisher, message) {
	//this.nameはpub?sub?  意図しているのはsubですが、うまくいくでしょうか？
	console.log(publisher + ' told ' + this.name + ' "' + message + '."');
};


/**
 * テスト開始
 *
 * 	publisherのインスタンス化
 *
 *	定期的にsend('hello')のイベント発生
 *
 * 	シナリオで分岐　ここから
 * 
 * 		subscriberのインスタンス化。addListener
 * 
 * 		subscriberのremoveListener
 * 
 * 		subscriberの破棄
 * 	
 * 	シナリオで分岐　ここまで
 *
 * 	イベント発行を終了。publisherの破棄
 *
 * 	テスト終了
 */


//今回はdeleteを使用してインスタンスの解放をして
//メモリリークの調査をしますので、ルートオブジェクトを設定します
var root = {};

//発行クラスのインスタンス化
root.pub = new Publisher('pub');

//定期的にイベントの発行
var pubIntId = setInterval(function() {
	root.pub.send('hello');
}, 2000);

//シナリオ1-5
switch (scenarioNo) {
case 1:
	console.log('シナリオ１');

	root.sub = new Subscriber('sub');
	// this.nameはpubになってしまっていて意図と異なる
	root.pub.on('send', root.sub.onSend);

	//イベントハンドラ
	setTimeout(function () {
		root.pub.removeListener('send', root.sub.onSend);
		console.log('sub removeListener!');
	}, 3000);

	//削除
	setTimeout(function () {
		delete root.sub;
		console.log('delete root.sub!');
	}, 5000);

	break;

case 2:
	console.log('シナリオ２');
	
	root.sub = new Subscriber('sub');
	//thisを束縛できているので
	root.pub.on('send', root.sub.onSend.bind(root.sub));

	//イベントハンドラ
	setTimeout(function () {
		//新たにbindしたらイベントハンドラの実体が新たに作成されるので実は解除出来ていない
		root.pub.removeListener('send', root.sub.onSend.bind(root.sub));
		//さらにthisの束縛のためsubがpublisherのリスナー配列に入ったままなのでメモリリークになってしまった
		//気づきにくいミスなんでこんなのがバグで入ったら最悪。見つけるの大変よー
		console.log('removeListener root.sub.onSend.bind(root.sub)? failure...');
		console.log(root.pub._events); 
	}, 3000);

	//削除
	setTimeout(function () {
		//実はdeleteしてもroot.pub._eventsの中で参照が残っている。参照カウンタが0になっていない
		delete root.sub;
		console.log('delete root.sub');
	}, 5000);

	break;

case 3:

	console.log('シナリオ３');

	root.sub = new Subscriber('sub');
	//this束縛後に一時変数に格納してみた
	var handler = root.sub.onSend.bind(root.sub);
	root.pub.on ('send', handler);

	setTimeout(function () {
		// OK 今度は解除できている
		root.pub.removeListener('send', handler);
		console.log('removeListener root.sub.onSend.bind(root.sub)!'); 
		console.log(root.pub._events);
	}, 3000);

	setTimeout(function () {
		//deleteしているのでGC対象になったはず。。。でも本当はなっていない
		delete root.sub;
		console.log('delete root.sub');
	}, 5000);

	break;

case 4:
	/**
	 * いよいよ解決
	 * 
	 * 解除は出来て。メモリリークも回避！ 
	 * どうやらシナリオ３ではsetTimeoutの内のfunctionで変数のhandlerがクロージャーぽく参照されてしまっていたみたいです。(たぶん。。。）
	 * 連鎖してhandlerがsubを取っ捕まえていたらしい。
	 * こんなのどうやって気がつけと。。。javascriptって油断ならねぇ。。。
	 * つまりfunctionがある場合はその外側のスコープで安易に変数を定義してははまってしまうって事？
	 */
	console.log('シナリオ４');

	root.sub = new Subscriber('sub');
	//新しいイベントハンドラをsubのメンバに格納しただけ
	root.sub.onSend2 = root.sub.onSend.bind(root.sub);
	root.pub.on ('send', root.sub.onSend2);

	setTimeout(function () {
		//もちろんこちらもメンバを指定して解除
		root.pub.removeListener('send', root.sub.onSend2);
		console.log('removeListener root.sub.onSend.bind(root.sub)!');
		console.log(root.pub._events);

	}, 3000);

	setTimeout(function () {
		delete root.sub;
		console.log('delete root.sub');
	}, 5000);

	/**
	 * よかったよかった。ってbindとか面倒くさくない？　＝＞　シナリオ５ですっきり解決
	 */

	break;

case 5:
	console.log('シナリオ５');

	/**
	 * だいたい、イベントを登録する際に毎回こんなこと(bind)をやってられないつーの。
	 * シナリオ１みたいに簡単にできないのかよ　＞＞＞　クロージャーを使えば出来ました
	 * 
	 * 発想を変えてみてイベントハンドラを新たに作成し直してみるのはどう？
	 * もちろんイベントでsubscriberのメンバを使う事が出来るようになるよ
	 * 本当はこのシナリオではpublisherのメンバがthisでとれるんで
	 * イベントハンドラの第２引数のpublisherは不要なんだけどpubから送られてくるで、
	 * まあ今は気にしない
	 */

	//まずはprototypeにイベントハンドラを動的に作る為のメソッドを作成
	Subscriber.prototype.addHandlerOnSend = function () {
		// これがポイントっぽい
		var self = this; 
		self.onSend = function (publisher, message) { 
			console.log(this.name + ' told ' + self.name + ' "' + message + '."');
		};
		return self; //メソッドチェーン用
	};
	//addHandlerOnSendのおかげでonSend内でもsubscriber(変数self)のメンバが使用出来るようになった
	root.sub = new Subscriber('sub').addHandlerOnSend();

	//ここからはイベントを登録する実際のコード
	//シナリオ１とかわらない事を確認

	//普段通りon
	root.pub.on ('send', root.sub.onSend);

	setTimeout(function () {
		//普段通りremoveListener
		root.pub.removeListener('send', root.sub.onSend);
		console.log('removeListener root.sub.onSend!'); 
		console.log(root.pub._events);
	}, 3000);

	setTimeout(function () {
		//もちろんメモリリークにならずに済んだ！
		delete root.sub;
		console.log('delete root.sub');
	}, 5000);

	/**
	 * 最後にまとめます
	 *
	 * 		実装ではaddHandlerOnSend()をコンストラクタ内で処理する事になると思います
	 * 		new Subscriber(name).addHandlerOnSend() よりnew Subscriber(name)のほうが
	 * 		しっくりきますので。
	 * 
	 * 実はこれシナリオ４に比べても良い点があります
	 * 	シナリオ4では
	 * 		誤ってbind前のメソッドをイベントハンドラにする事ができるのでミスしやすいです
	 * 		しかもなんとなくそれらしい動作をするように見えるときがあるからバグに見えません
	 * 		でもイベントの登録する実際のコードだけでは間違ったコードだと判断が難しいです
	 *
	 * 	シナリオ５では
	 * 		シナリオ１と同じように記述出来る
	 * 		動的に設定出来るのは何もthisだけではありません。コンストラクタの引数を利用して
	 * 		他のオブジェクトでもクロージャーならなんでも利用出来ます。
	 *
	 * 	でも気をつけるべき点
	 * 		sub自体を使用しなくなった際には必ずremoveListenerをしないとsubはGCの対象になりません
	 * 		pub._eventsの中でsubは参照され続けます。
	 * 		しかもイベントも受け取り続けてるので、イベントが発火したタイミングになって誤作動する可能性があります
	 * 		イベントなのですぐに動作不良を起こさない可能性があるので十分に気をつけないといけません
	 * 		試しに「普段通りremoveListener」の次行をコメントアウトした動作を試してみたら挙動が変なことを確認出来ます
	 * 		またpubがdeleteされるまでsubも解放されないことが判明します
	 * 		
	 * 		addHandlerOnSendはprototypeメンバなのでメモリの消費量は小さいですが、
	 * 		onSendはインスタンスのメンバですのでメモリの使用量は若干多くなります
	 */
}

//発行を停止してインスタンスの削除
setTimeout(function () {
	clearInterval(pubIntId);
	delete root.pub;
	console.log('delete root.pub!');
}, 7000);

//終了処理
setTimeout(function() { 
	console.log('finish');
}, 10000);


```