JavaScriptの型などの判定いろいろ

今更その話題かよ！と思いますが、自分のためのメモとして残しておきます。

#typeof を使う

`typeof` 演算子はデータ型を示す文字列を返しますが、大きなひとくくりとしての型なので厳密な型判定ができないのはご存知だと思います。

|オペランド|結果|
|:--|:--|
|`undefined`|"undefined"|
|`null`|"object"|
|`true` / `false`|"boolean"|
|`['array']`|"object"|
|`{key: 'value'}`|"object"|
|`NaN`|"number"|
|`1234`|"number"|
|`new Number()`|"object"|
|`'abcd'`|"string"|
|`new String()`|"object"|
|`function () {}`|"function"|
|`() => {}`|"function"|
|`function* () {}`|"function"|
|`async function () {}`|"function"|
|`new Promise(() => {})`|"object"|
|`Symbol()`|"symbol"|
|`new Map()`|"object"|
|`new Set()`|"object"|

`null` や配列が `"object"` だったり、普通は使わないけれども `new Number()` や `new String()` も `"object"` だったりするので、ややこしいです。

ちなみに、`'use strict'` じゃない時、関数の `this` に文字列や数値を渡すと `"object"` になります。

```js:JavaScript
function typeOf01() {
  console.log(typeof this);
}

function typeOf02() {
  'use strict';
  console.log(typeof this);
}

// object になる
typeOf01.call('すっごーい！');  // "object"
typeOf01.call(15820621);       // "object"

// 意図したとおりになる
typeOf02.call('たーのしー！');  // "string"
typeOf02.call(17760704);       // "number"
```

`jQuery.each()` でオブジェクトを扱っている場合注意しないといけません。

##typeof だけで判定しても支障がないもの

- `undefined`
- `true` / `false`
- `Symbol()`

#Object.prototype.toString.call() を使う

`Object.prototype.toString.call()` を使うと `[object 何らかの型]` という文字列を取得できて、そこからそのオブジェクトの型がわかるというのもご存知だと思います。

```js:JavaScript
var toString = Object.prototype.toString;
function typeOf(obj) {
  return toString.call(obj).slice(8, -1).toLowerCase();
}
```

|オペランド|結果|
|:--|:--|
|`undefined`|"undefined"|
|`null`|"null"|
|`true` / `false`|"boolean"|
|`['array']`|"array"|
|`{key: 'value'}`|"object"|
|`NaN`|"number"|
|`1234`|"number"|
|`new Number()`|"number"|
|`'abcd'`|"string"|
|`new String()`|"string"|
|`function () {}`|"function"|
|`() => {}`|"function"|
|`function* () {}`|"generatorfunction"|
|`async function () {}`|"asyncfunction"|
|`new Promise(() => {})`|"promise"|
|`Symbol()`|"symbol"|
|`new Map()`|"map"|
|`new Set()`|"set"|

だいたいのものはこれで十分ですね。`NaN` など、一部気を付けないといけないものがありますが。

#Object.prototype.toString.call() を使わないで判定

ネイティブで判定できる関数が用意されていたり、他の方法もあるよというものたちです。

##Array

配列の場合、`Object.prototype.toString.call()` を使うよりもネイティブで実装されている `Array.isArray()` を使う方が簡単に真偽値を取ることができ、分かりやすいコードになります。
IE8以下では使えません。

```js:JavaScript
Array.isArray([]);           // true
Array.isArray(new Array());  // true
Array.isArray({});           // false
Array.isArray('array');      // false

// こういう方法もあったりした（非推奨）
([] instanceof Array);       // true
({} instanceof Array);       // false
```

##NaN

`NaN` は 'Not a Number' と名乗っておきながら `Object.prototype.toString.call()` でも `"number"` なのでめんどくさいですね。この判定もネイティブで `isNaN()` や `Number.isNaN()` が実装されています。
しかし、`isNaN()` と `Number.isNaN()` は**違いがあります**。

###isNaN()

この関数は引数が数値ではないかどうかを判定します。
なので、`NaN` 以外も `true` になる場合があります。

```js:JavaScript
isNaN(NaN);              // true
isNaN(undefined);        // true ←！？
isNaN([]);               // false
isNaN({});               // true ←！？
isNaN(null);             // false
isNaN(true);             // false
isNaN(false);            // false
isNaN(1234);             // false
isNaN('');               // false
isNaN('1234');           // false
isNaN('たべないよー！');  // true ←！？
```

どうしてこのような結果になるのでしょうか。
`isNaN()` は**渡された引数を数値に変換できるかどうかを試みる**関数なんです。数値に変換できなかったら `true` を返すわけですね。

###Number.isNaN()

これこそが `isNaN()` と違い、**引数が `NaN` かどうか**を確かめるメソッドになります。
しかし、これは ECMAScript 2015 の仕様なので、最新のブラウザ（ChromeとFirefox）でしか使えません。

```js:JavaScript
Number.isNaN(NaN);              // true
Number.isNaN(undefined);        // false
Number.isNaN({});               // false
Number.isNaN('たべないよー！');  // false
```

代替（ポリフィル）として以下が使えます。

```js:JavaScript
Number.isNaN = Number.isNaN || function (obj) {
  // typeof NaN === 'number' -> true と、
  // NaN !== NaN -> true を利用する
  return typeof obj === 'number' && obj !== obj;
}
```

##関数とアロー関数

ECMAScript 2015 からアロー関数 `() => {}` が使用できるようになりましたが、普通の関数との違いとして 'this' がグローバルなどを指し、'arguments' が取得できない、'prototype' を持たないなど違いがあります。
これの判定も、その性質を利用すれば簡単ですね。（実際にこういうことはしないとは思いますが）

```js:JavaScript
var toString = Object.prototype.toString;

function isFunction(obj, notArrow) {
  return toString.call(obj) === '[object Function]' && (!notArrow || 'prototype' in obj);
}

isFunction(isFunction);            // true
isFunction(function () {});        // true
isFunction(() => {});              // true

isFunction(isFunction, true);      // true
isFunction(function () {}, true);  // true
isFunction(() => {}, true);        // false

// ネイティブなメソッドも false になるかもしれない
isFunction(eval, true);            // false
isFunction(setTimeout, true);      // false
```

##window

`window` も実は厄介です。

```js:JavaScript
var toString = Object.prototype.toString;

console.log(typeof window);  // "object"

toString.call(window);
// Chrome: "[object Window]"
// Firefox: "[object Window]"
// Edge: "[object Window]"
// IE11: "[object Window]"
// IE8: "[object Object]"
```

IE8は切り捨ててしまえとは思いますが、それができない場合はこんな方法があります。

```js:JavaScript
function isWindow(obj) {
  return !!obj && obj === obj.window;
}

isWindow(window);  // true
```

`window` に `window.window` があるのを利用したものです。

##DOM

`document` やHTML要素などのノードを判定するには `Object.prototype.toString.call()` を使用するより `Node.nodeType` で行えます。

```js:JavaScript
function isDocument(obj) {
  // document の nodeType は 9
  return !!obj && obj.nodeType === 9;
}

function isElement(obj) {
  // XML/HTML要素の nodeType は 1
  return !!obj && obj.nodeType === 1;
}

isDocument(document);       // true
isDocument(document.body);  // false

isElement(document);        // false
isElement(document.body);   // true
```

##おまけ：jQueryオブジェクトか調べる

jQueryオブジェクトは `Object.prototype.toString.call()` を使っても `[object Object]` になってしまいますが、`instanceof` 演算子である程度判定できます。

```js:JavaScript
var
  paragraph01 = $('.entry p'),
  paragraph02 = document.querySelectorAll('.entry p'),
  obj = {};

(paragraph01 instanceof jQuery);  // true
(paragraph02 instanceof jQuery);  // false
(obj instanceof jQuery);          // false
```