JavaScriptの「型」の判定について

JavaScriptの型にまつわるややこしい仕様について、簡単にまとめてみたいと思います。

## 5つのプリミティブ型を判定 ###################################
JavaScriptにおける型とは、数値(number)、文字列(string)、ブール値(boolean)、null、undefinedの5つのプリミティブ型を指します。これらはtypeof演算子を用いて判別する事が出来ます。ただし、nullだけは仕様に反して`'object'`を返します。

```javascript
typeof 'str';     // 'string'
typeof 1;         // 'number'
typeof true;      // 'boolean'
typeof null;      // 'object' (ECMAScript標準規格によれば独自の型のはずだが...)
typeof undefined; // 'undefined'
```

その他は全てオブジェクトです。ただし、関数オブジェクトだけは特別に`'function'`を返します。

```javascript
typeof {}; // 'object'
typeof []; // 'object'
typeof function() {}; // 'function' 関数オブジェクトは特別扱い
```

## オブジェクトの[[Class]]を用いた判定 ###########################
プリミティブ型は判別できましたが、これでは様々なオブジェクト(Object、Array等)の判別は出来ません。しかし、「型」の判定をしたいと言う場合、実際にはArrayやObjectの区別をしたい場合が多いかと思います。
そこで、`Object.prototype.toString`を用いて、オブジェクトの`[[Class]]`内部プロパティを取得する方法がしばしばとられます。出力は、`[object [[Class]]]`という形式になります。
[参考: Qiitaページ](http://qiita.com/Layzie/items/465e715dae14e2f601de)

```javascript
var toString = Object.prototype.toString

toString.call({});                // [object Object]  
toString.call([]);                // [object Array]
toString.call(function() {});    // [object Function]
toString.call(new Error());       // [object Error]
toString.call(new Date());        // [object Date]
toString.call(JSON);              // [object JSON]
toString.call(Math);              // [object Math]
toString.call(new RegExp());      // [object RegExp]
toString.call(new String('str')); // [object String]
toString.call(new Number(1));     // [object Number]
toString.call(new Boolean(true)); // [object Boolean]
```

最後の3つは、それぞれ文字列(string)、数値(number)、ブール値(boolean)のラッパーオブジェクトになります。ラッパーオブジェクトというのはstring, number, booleanの3つのプリミティブな値に対してメソッドを呼び出した際に、メソッド実行用に一時的に生成されるオブジェクトです。その為、コンストラクタを使わずに`toString`を呼び出す事が出来ます。

```javascript
toString.call('str'); // [object String]
toString.call(1);     // [object Number]
toString.call(true);  // [object Boolean]
```

また、ECMAScript5からは、`null`や`undefined`も`[[Class]]`を返すようになりました。
[参考: MDN公式ドキュメント toString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString)

```javascript
toString.call(null);      // [object Null]
toString.call(undefined); // [object Undefined]
```

これで、標準コンストラクタで生成されたオブジェクトは判別出来る事が分かりました。

## constructorプロパティを用いた判定 ###########################
ただし、問題はまだあります。自分で実装したコンストラクタから生成したオブジェクトは、`Object.prototype.toString`を使っても`[object Object]`にしかなりません。

```javascript
function MyClass() {}
var obj = new MyClass();
Object.prototype.toString.call(obj); // [object Object]
```

これを判別する一つの方法として、constructorプロパティを用いるといった方法が考えられます。

```javascript
obj.constructor === MyClass; // true
```

ただし、文字列としては取得出来ず、さらに`constructor`プロパティが上書きされる危険性もあります。また、コンストラクタの`prototype`プロパティを書き換えていると、おかしな事になる場合もあります。

```javascript
obj.constructor === 'MyClass'; // false

obj.constructor = Array;     // constructorプロパティを上書き
obj.constructor === MyClass; // false

function MyClass2() {}  // 新しいコンストラクタを定義
MyClass2.prototype = {}; // コンストラクタのprototypeを書き換え
var obj2 = new MyClass2();
obj2.constructor === MyClass2; // false
obj2.constructor === Object;   // true
```

確実な判定方法とは言えないかもしれません。

## instanceof演算子を用いた判定 ################################
instanceof演算子は、オブジェクトがどのコンストラクタから生成されたかを判別する為に使われます。オブジェクトの「広義の型」の判定に使えそうです。

```javascript
function MyClass() {}
var obj = new MyClass();
obj instanceof MyClass; // true
obj instanceof Array;   // false
```

ここで、instanceof演算子の挙動について考えてみます。instanceof演算子は、内部的にはオブジェクト自身のプロトタイプチェーンにコンストラクタの`prototype`が含まれているかをチェックしています。
[参考: MDN公式ドキュメント instanceof](https://developer.mozilla.org/ja/docs/JavaScript/Reference/Operators/instanceof)

```javascript
obj instanceof MyClass; // true
obj.__proto__ === MyClass.prototype; // true

obj instanceof Object;  // true
obj.__proto__.__proto__ === Object.prototype; // true
```

その為、オブジェクトのプロトタイプが書き換えられた場合には`instanceof`の返り値も変わってしまいます。

```javascript
obj.__proto__ = Array.prototype;
obj instanceof MyClass; // false
obj instanceof Array; // true
```

よって、これも確実な判定方法とはならないでしょう。

## 結論 #####################################################
標準コンストラクタから生成されたオブジェクトの判定には、`Object.prototype.toString`を使うと良いでしょう。自前のコンストラクタから生成されたオブジェクトの判定にはいくつか方法がありますが、上であげた2つはどちらも確実な方法とはいえません。\_\_proto\_\_プロパティの書き換えは決してしない等の規約を決めた上で、判定を行うと良いでしょう。