TypeScriptでオブジェクトかどうかを判定する関数

本稿ではTypeScriptで値がオブジェクトかどうかを判定する関数の考え方を述べる。

## そもそもオブジェクトとは？

TypeScriptはJavaScriptのスーパーセットなので、「TypeScriptにおけるオブジェクトとは？」という疑問は「JavaScriptのオブジェクトとは？」と言い換えることができる。

### JavaScriptのオブジェクトとは？

JavaScriptのオブジェクトを考える上で、オブジェクトじゃないデータ型は何があるかを知る必要がある。

> 最新の ECMAScript 標準仕様では 7 つのデータ型が定義されています。
> 
> * 6 種類のプリミティブデータ型:
>   * Boolean
>   * Null
>   * Undefined
>   * Number
>   * String
>   * Symbol (ECMAScript 6 の新データ型)
> * Object
>
> *── [JavaScript のデータ型とデータ構造 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures#Data_types)より抜粋*

これによれば、`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 not `number`, `string`, `boolean`, `symbol`, `null`, or `undefined`.
> 
> ── [Basic Types · TypeScript](https://www.typescriptlang.org/docs/handbook/basic-types.html#object)

## `typeof`演算子とデータ型の分類の違い

JavaScriptの`typeof`演算子は、与えられたオペランドの型を表す文字列を返す。

```js
typeof operand
```

例えば、`typeof {a: 1}`の演算結果は`"object"`になる:

```js
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`の仕様を踏まえた上で作るオブジェクトかどうかを判定する関数は次のようになる:

```ts
const isObject = (x: unknown): boolean =>
  x !== null && (typeof x === 'object' || typeof x === 'function')
```

タイプガードに使うことも考えると、戻り値を次のようにしておくと良さそうだ:

```ts
const isObject = (x: unknown): x is object =>
  x !== null && (typeof x === 'object' || typeof x === 'function')
```

## オープンソースのライブラリはどういう実装になってる？

参考までに、実績のあるオープンソースライブラリで、オブジェクトかどうかを判定する関数がどうなっているかも見ておこう。

### underscore

JavaScriptでの実装:

```underscore.js
  // Is a given variable an object?
  _.isObject = function(obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj;
  };
```

── [GitHubのunderscore.js](
https://github.com/jashkenas/underscore/blob/d5fe0fd4060f13b40608cb9d92eda6d857e8752c/underscore.js#L1319-L1323)より抜粋。

TypeScriptでの定義:

```underscore/index.d.ts
        isObject(object: any): boolean;
```

── [DefinitelyTypedのunderscore/index.d.ts](
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/020e04aeb2d972477a510163b489e0452807cb40/types/underscore/index.d.ts#L3878)より抜粋。

### lodash

JavaScriptでの実装:

```isObject.js
function isObject(value) {
  const type = typeof value
  return value != null && (type == 'object' || type == 'function')
}
```

── [GitHubのisObject.js](https://github.com/lodash/lodash/blob/4ea8c2ec249be046a0f4ae32539d652194caf74f/isObject.js#L24-L27)より抜粋。

TypeScriptでの定義:

```lodash/common/lang.d.ts
        isObject(value?: any): value is object;
```

── [DefinitelyTypedのlodash/common/lang.d.ts](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/83e5c8c944d18bf5ea542428a75dd79934be5333/types/lodash/common/lang.d.ts#L1227)より抜粋。


### Angular.js

JavaScriptでの実装:

```angular.js
function isObject(value) {
  // http://jsperf.com/isobject4
  return value !== null && typeof value === 'object';
}
```

── [GitHubのangular.js](https://github.com/angular/angular.js/blob/060bcdeeb9f5181fb885417f68429c86a8e59eb2/src/Angular.js#L537-L540)より抜粋。


* functionを考慮していないのは仕様なのかどうなのかは不明。

TypeScriptでの定義:

```angular/index.d.ts
        isObject(value: any): value is Object;
        isObject<T>(value: any): value is T;
```

── [DefinitelyTypedのangular/index.d.ts](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/1963560324079a453d87384c64185dfe130d3097/types/angular/index.d.ts#L139)より抜粋。