本稿ではTypeScriptで値がオブジェクトかどうかを判定する関数の考え方を述べる。
そもそもオブジェクトとは?
TypeScriptはJavaScriptのスーパーセットなので、「TypeScriptにおけるオブジェクトとは?」という疑問は「JavaScriptのオブジェクトとは?」と言い換えることができる。
JavaScriptのオブジェクトとは?
JavaScriptのオブジェクトを考える上で、オブジェクトじゃないデータ型は何があるかを知る必要がある。
最新の ECMAScript 標準仕様では 7 つのデータ型が定義されています。
- 6 種類のプリミティブデータ型:
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (ECMAScript 6 の新データ型)
- Object
これによれば、Function
やArray
もオブジェクトのひとつになる。
TypeScriptのobject
とは?
TypeScriptにはobject
型があるが、これがJavaScriptのオブジェクトと一致するのか見ておこう。
公式ドキュメントによれば、「object
はプリミティブ型ではないものを指す型で、例えば、number
、string
、boolean
、symbol
、null
、undefined
ではないもの」ということなので、JavaScriptのオブジェクトと同じだということが分かる。
object
is a type that represents the non-primitive type, i.e. anything that is notnumber
,string
,boolean
,symbol
,null
, orundefined
.
typeof
演算子とデータ型の分類の違い
JavaScriptのtypeof
演算子は、与えられたオペランドの型を表す文字列を返す。
typeof operand
例えば、typeof {a: 1}
の演算結果は"object"
になる:
typeof {a: 1} //=> "object"
前述したとおり、JavaScriptには7つのデータ型区分が定義されているが、それらとtypeof
が返す文字列とは必ずしも1対1で一致しない。
型 | 返値 | 7つのデータ型区分 |
---|---|---|
未定義 | undefined | Undefined (プリミティブ) |
Null | object | Null (プリミティブ) |
真偽値 | boolean | Boolean (プリミティブ) |
数値 | number | Number (プリミティブ) |
文字列 | string | String (プリミティブ) |
シンボル (ECMAScript6 で新しく導入) | symbol | Symbol (プリミティブ) |
ホストオブジェクト (provided by the JS environment) | 実装に依存 | ??? |
関数オブジェクト (implements [[Call]] in ECMA-262 terms) | function | Object |
その他のオブジェクト | object | Object |
ここで注目したいのが下記の2点だ。
-
null
は7つのデータ型区分ではNullだが、typeof
演算子ではobjectになる。 - 関数オブジェクトは7つのデータ型区分ではObjectだが、
typeof
演算子ではfunctionになる。
したがって、typeof value === 'object'
がtrue
なら、value
が「JavaScriptのオブジェクトである」とは言えないわけだ。value
がnull
のときや関数のときが反例になる。
オブジェクトかどうかを判定する関数
typeof
の仕様を踏まえた上で作るオブジェクトかどうかを判定する関数は次のようになる:
const isObject = (x: unknown): boolean =>
x !== null && (typeof x === 'object' || typeof x === 'function')
タイプガードに使うことも考えると、戻り値を次のようにしておくと良さそうだ:
const isObject = (x: unknown): x is object =>
x !== null && (typeof x === 'object' || typeof x === 'function')
オープンソースのライブラリはどういう実装になってる?
参考までに、実績のあるオープンソースライブラリで、オブジェクトかどうかを判定する関数がどうなっているかも見ておこう。
underscore
JavaScriptでの実装:
// Is a given variable an object?
_.isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
};
── GitHubのunderscore.jsより抜粋。
TypeScriptでの定義:
isObject(object: any): boolean;
── DefinitelyTypedのunderscore/index.d.tsより抜粋。
lodash
JavaScriptでの実装:
function isObject(value) {
const type = typeof value
return value != null && (type == 'object' || type == 'function')
}
── GitHubのisObject.jsより抜粋。
TypeScriptでの定義:
isObject(value?: any): value is object;
── DefinitelyTypedのlodash/common/lang.d.tsより抜粋。
Angular.js
JavaScriptでの実装:
function isObject(value) {
// http://jsperf.com/isobject4
return value !== null && typeof value === 'object';
}
── GitHubのangular.jsより抜粋。
- functionを考慮していないのは仕様なのかどうなのかは不明。
TypeScriptでの定義:
isObject(value: any): value is Object;
isObject<T>(value: any): value is T;