カスタムオブジェクトの定義について

JavaScriptのprototype周りを勉強しながら、過去・現在の自分で書いたコードを見直したところ、
ちょっとまずい構成をしていたことが判明しました。例として以下のコードで考えます。

//ストップウオッチ
var StopWatch = function(){
	var _st = undefined;
	var _ed = undefined;

	var _start = function(){
		_st = new Date();
	}
	var _stop = function(){
		_ed = new Date();
	}
	var _clear = function(){
		_st = undefined;
		_ed = undefined;
	}
	var _getElapsedMilliseconds = function(){
		if(_st === undefined || _st === undefined)
			return -1;
		return _ed.getTime() - _st.getTime();
	}
	
	return {
		start : _start,
		stop : _stop,
		clear : _clear,
		getElapsedMilliseconds : _getElapsedMilliseconds
	}
}

1) まずい点 - new演算子

まずい点というよりは、理解不足による無駄な処理を行っていた点です。
StopWatchコンストラクタ関数の戻り値が存在します。
この時の「return { ... }」 は return new Object();」 と等価です。
ということは、関数を(new関係なく)呼び出すだけでオブジェクト(インスタンス)が生成できてしまうのです。*1
あと、これはコンストラクタ関数じゃないので、createStopWatch()みたいな感じに名称変更も行うことも必要になるでしょう。

//等価
var sw1 = new StopWatch();
var sw2 = StopWatch();

2) まずい点 - オブジェクト(インスタンス)のサイズ

各オブジェクト(インスタンス)が全メンバとメソッドを持っているので、大量にオブジェクト(インスタンス)を生成するとメモリ量が圧迫される場合があります。
プロトタイプ拡張する形式でカスタムオブジェクト定義すれば、オブジェクト(インスタンス)では各自の状態だけを持たせるようにできるため、使用メモリ量が改善が期待できます。

3) まずい点 - オブジェクト(インスタンス)がカスタムオブジェクトでは無い

コンストラクタ関数の戻り値で、new Object()して、そこにプロパティをいろいろ設定したものをインスタンスとして使用しています。
これだとObjectとして認識されていまうようです。(StopWatchオブジェクトであることを期待していました。)

var sw = new StopWatch();
console.log(sw);

★出力結果
f:id:ham007:20131010163216p

4) プロトタイプを意識したオブジェクト

定義例として以下のような記述が最も理解しやすいんじゃないかなと思います。
(ここを参考にしました)

//ストップウオッチ
var StopWatch2 = (function(){

	function StopWatch2(){
		this._st = undefined;
		this._ed = undefined;
	}

	function _start(){
		this._st = new Date();
	}
	
	function _stop(){
		this._ed = new Date();
	}
	
	var _clear = function(){
		this._st = undefined;
		this._ed = undefined;
	}
	var _getElapsedMilliseconds = function(){
		if(this._st === undefined || this._ed === undefined)
			return -1;
		return this._ed.getTime() - this._st.getTime();
	}
	
	StopWatch2.prototype = {
		constructor : StopWatch2,
		start : _start,
		stop : _stop,
		getElapsedMilliseconds : _getElapsedMilliseconds
	}
	
	return StopWatch2;
})();

5) プロトタイプを意識したオブジェクト - メリット

  • オブジェクト(インスタンス)サイズ量が小さくなる。オブジェクト共通で使うものはprototype経由で参照できる。

6) プロトタイプを意識したオブジェクト - デメリット

  • メンバの隠蔽が出来ない。JavaScriptにアクセス修飾の概念が存在しない為。やろうと思うとクロージャを使う必要あり。

7) 定義方法の使い分け

一長一短ありますので、状況に応じて使い分けするのもよいかと思いますが、
常に「プロトタイプを意識したオブジェクト」の定義方法を採用するのでも問題ないかと思います。
メンバの隠蔽の件については、コーディング規約など、運用でカバーできますしね。。

*1:コンストラクタ関数に戻り値が無い場合は、生成したthisに相当するオブジェクトが返されます。