角待ちは対空

おもむろガウェイン

TypeScriptの`Object`型と`object`型と`{}`型の使い分けについて

TypeScriptには似たような型としてObject型とobject型と{}型が存在します。

let o1: Object;
let o2: object;
let o3: {};

今回はこの3つの使い分け、あるいはobject型導入の経緯についてです。

JavaScriptのデータ型

データ構造 - JavaScript | MDNを読めば分かるように、

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol

の6種のプリミティブ型を持つプリミティブ値とオブジェクトでJavaScriptは成り立っています。

TypeScriptにおけるobject型とはここでいうプリミティブ型以外を表現しています。

object型はいつ使われるのか

いつ役に立つかというとObject.create()の定義です。

TypeScript/lib.d.ts at master · Microsoft/TypeScript · GitHub

/**
  * Creates an object that has the specified prototype, and that optionally contains specified properties.
  * @param o Object to use as a prototype. May be null
  * @param properties JavaScript object that contains one or more property descriptors.
  */
create(o: object | null, properties: PropertyDescriptorMap & ThisType<any>): any;

Object.create()の第一引数はobject | nullです。したがってObject.create('str')Object.create(4403)などはエラーとなります。

Object型とは何か

これは単純にObjectオブジェクトの構造を記述するためのInterfaceです。

追記

Objectオブジェクトの型はObjectConstructorなので、Object型は全てのオブジェクトに共通する構造を記述するInterfaceといったほうが正しかったです。

{}型とは何か

これは{}のようなオブジェクトリテラルで書かれる、プロパティを持たないオブジェクトの型を表現しています。ただしJavaScriptなので{}オブジェクトはプロトタイプチェーンの結果Objectオブジェクトのメソッドやプロパティが利用できるはずなので、結果として{}型とObject型は実質同じ型となっています。

使い分け

というわけで普通我々が使う場合は大体{}型です。

オブジェクト共通の構造を示したい場合はObject型を使うのが良いんだと思いますがシチュエーションは思い浮かびません。

object型を使うのはプリミティブ型ではいけない場合です。つまりどういうことは導入の経緯とともに見ていきます。

object型導入の経緯

プリミティブ型でもObject型に定義されたメソッドやプロパティが使える

JavaScriptの仕様上true.toString()のなどの呼び出しが可能です(trueはプリミティブ値なのにObjectオブジェクトのメソッドを呼んでいる)。何故これが可能かと言えば暗黙的にオブジェクトが生成されているからです。つまりtrue.toString()(new Boolean(true)).toString()と同等です。

この暗黙的生成を表現するためTypeScriptではプリミティブ型はObject型の構造も含んでいます。

この仕様で起こる問題

TypeScriptはStructural subtypingを採用しているので、Object.create()の型定義ができないことになります。

つまり

create(o: Object | null, properties: PropertyDescriptorMap & ThisType<any>): any;

としてしまうとObject型を満足する変数やリテラルを引数に取れることになります。すると例えば"str"(string型)は先ほど説明した仕様によりObject型を満足させられるのでObject.create("str")はTypeScript上ではエラーとして検出されません。JavaScriptとして実行された時初めてエラーになります。

これがobject型という非プリミティブ型を表現する型が導入された経緯です。

使い分けないと困るのか

object型が導入された経緯みたいな話はありますが、{}型とObject型に関して言えばどっち使っても極端に困ることないと思います(ただしIDEの補完については多少差がでます)。気分の問題と言えば気分の問題ですが似たようなものがある意味くらいは知っておく良いかと思います。