Edited at

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

More than 1 year has passed since last update.

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


5つのプリミティブ型を判定

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

typeof 'str';     // 'string'

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

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

typeof {}; // 'object'

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


オブジェクトの[[Class]]を用いた判定

プリミティブ型は判別できましたが、これでは様々なオブジェクト(Object、Array等)の判別は出来ません。しかし、「型」の判定をしたいと言う場合、実際にはArrayやObjectの区別をしたい場合が多いかと思います。

そこで、Object.prototype.toStringを用いて、オブジェクトの[[Class]]内部プロパティを取得する方法がしばしばとられます。出力は、[object [[Class]]]という形式になります。

参考: Qiitaページ

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を呼び出す事が出来ます。

toString.call('str'); // [object String]

toString.call(1); // [object Number]
toString.call(true); // [object Boolean]

また、ECMAScript5からは、nullundefined[[Class]]を返すようになりました。

参考: MDN公式ドキュメント toString

toString.call(null);      // [object Null]

toString.call(undefined); // [object Undefined]

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


constructorプロパティを用いた判定

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

function MyClass() {}

var obj = new MyClass();
Object.prototype.toString.call(obj); // [object Object]

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

obj.constructor === MyClass; // true

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

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演算子は、オブジェクトがどのコンストラクタから生成されたかを判別する為に使われます。オブジェクトの「広義の型」の判定に使えそうです。

function MyClass() {}

var obj = new MyClass();
obj instanceof MyClass; // true
obj instanceof Array; // false

ここで、instanceof演算子の挙動について考えてみます。instanceof演算子は、内部的にはオブジェクト自身のプロトタイプチェーンにコンストラクタのprototypeが含まれているかをチェックしています。

参考: MDN公式ドキュメント instanceof

obj instanceof MyClass; // true

obj.__proto__ === MyClass.prototype; // true

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

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

obj.__proto__ = Array.prototype;

obj instanceof MyClass; // false
obj instanceof Array; // true

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


結論

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