# 型の判定

# はじめに

JavaScript は弱い型付けの言語であり、型の取り扱いについては注意が必要な部分がある。

# typeof演算子

返り値
未定義値 "undefined"
null "object"
真偽値 "boolean"
数値 "number"
文字列 "string"
シンボル "symbol"
ホストオブジェクト
(ブラウザ、Node.js)
実装に依存
関数オブジェクト "function"
その他のオブジェクト "object"

# 判定できるもの

  • 真偽値 "boolean"
  • シンボル "symbol"
  • 関数オブジェクト "function"

# 大体判定できるもの

  • 未定義値 "undefined
  • 数値 "number"
  • 文字列 "string"

未定義値は、値が代入されていない変数も宣言がなされていない変数も"undefined"を返す。
ReferenceErrorを発生させずに未定義か調べたい場合に用いることができる。

console.log(typeof x);  // => "undefined"
let x;
console.log(typeof x);  // => "undefined"

数値と文字列については、new Number()new String()のように、new演算子でコンストラクタを呼び出すと"object"と判定されてしまう。
もっともこれらはリテラルで宣言するのが一般的なのであまり気にしなくてよい気はする。

const str1 = new String();        // String {""}
console.log(typeof str1);         // => "object"

const str2 = new String('hoge');  // String{"hoge"}
console.log(typeof str2);         // => "object"

const str3 = String(123);         // => "123"
console.log(typeof str3);         // "string"
const num1 = new Number();     // Number {0}
console.log(typeof num1);      // => "object"

const num2 = new String(123);  // Number {123}
console.log(typeof num2);      // => "object"

const num3 = Number('');       // => 0
console.log(typeof num3);      // "number"

# 判定には適さないもの

nullや配列、その他のオブジェクトはすべて"object"と判定される。

本来プリミティブな値であるnulltypeof演算子で"object"となるのは仕様上のバグである。
The history of “typeof null”

# Object.prototype.toString.call()

[object [型名]]という文字列を返す。

JavaScript においてすべての値はオブジェクトであり、Objectオブジェクトを継承している。
Object.prototypeを通して、Objectオブジェクトが元々もっているtoString()メソッドをFunction.prototype.call()で呼び出している。

Function.prototype.call()メソッドを用いることでthisのコンテクストを指定することができる。
型名を取得したい値をthisに束縛して、thisに対してObjectオブジェクトがもつtoString()メソッドを適用させている。
なお、Function.prototype.apply()も第一引数でthisを束縛できるので同様の結果が得られる。

{ key: value }型のオブジェクトは直接toString()メソッドを呼ぶと"[object Object]"を得ることができる。

const obj = {};
console.log(obj.toString());  // => "[object Object]"

対して、配列や Date 型ではObjectオブジェクトのtoString()メソッドをオーバーライドして、独自のtoString()メソッドを実装している。

const list = [1, 2, 3];
console.log(list.toString());  // => "1, 2, 3"

const date = new Date();
console.log(date.toString());  // => "Wed Nov 20 2019 16:32:05 GMT+0900 (日本標準時)"

これらのオブジェクトにもObjectオブジェクトのtoString()メソッドを適用させるためにObject.prototypeを通してtoString()メソッドを呼び出し、Function.prototype.call()thisコンテクストを束縛している。

const list = [1, 2, 3];
console.log(Object.prototype.toString.call(list));  // => "[object Array]"

const date = new Date();
console.log(Object.prototype.toString.call(date));  // => "[object Date]"

# プリミティブな値の判定

const whatIs = arg => {
  console.log(Object.prototype.toString.call(arg).slice(8, -1));
};

whatIs(undefined);     // => "Undefined"
whatIs(null);          // => "Null"

whatIs(true);          // => "Boolean"
whatIs(false);         // => "Boolean"

whatIs('hoge');        // => "String"
whatIs(new String());  // => "String"

whatIs(123);           // => "Number"
whatIs(new Number());  // => "Number"

whatIs(Symbol());      // => "Symbol"

new String()new Number()とした場合でも"String""Numberを得ることができる。

undefinednullについては ES5.1 以降でObject.prototype.toString()を呼び出すとそれぞれ"[object Undefined]""[object Null]"を得ることができるようになった。

配列オブジェクトはObjectオブジェクトを継承しているので、プロトタイプチェーンを辿っていってObjectオブジェクトのtoString()メソッドを呼び出している。

console.log([].__proto__.toString === Object.prototype.toString);  // => false
console.log([].__proto__.__proto__.toString === Object.prototype.toString);  // => true
console.log(Object.prototype.toString.call([]));  // => "[object Array]"

対して、nullundefinedprototypeプロパティや__proto__プロパティを持たず、実際はObjectプロパティのprototypeを参照しているわけではない。

互換性を考慮して後から例外的に追加されたものである。

console.log(undeinfed.prototype);  // TypeError: Cannot read property 'prototype' of undefined
console.log(null.__proto__);  // TypeError: Cannot read property '__proto__' of null

ECMAScript Language Specification - ECMA-262 Edition 5.1 | 15.2.4.2

# オブジェクトの判定

const whatIs = arg => {
  console.log(Object.prototype.toString.call(arg).slice(8, -1));
};

whatIs([]);                       // => "Array"
whatIs(new Int8Array());          // => "Int8Array"
whatIs(new Uint8Array());         // => "Uint8Array"
whatIs(new Uint8ClampedArray());  // => "Uint8ClampedArray"
whatIs(new Int16Array());         // => "Int16Array"
whatIs(new Uint16Array());        // => "Uint16Array"
whatIs(new Int32Array());         // => "Int32Array"
whatIs(new Uint32Array());        // => "Uint32Array"
whatIs(new Float32Array());       // => "Float32Array"
whatIs(new Float64Array());       // => "Float64Array"

whatIs({ key: 'value' });  // => "Object"

whatIs(function () {});         // => "Function"
whatIs(function* () {});        // => "GeneratorFunction"
whatIs(async function () {});   // => "AsyncFunction"
whatIs(async function* () {});  // => "AsyncGeneratorFunction"
whatIs((function* () {
  yield 1;
})());                        // => "Generator"
whatIs(new Promise(f => f));  // => "Promise"

whatIs(new Map());      // => "Map"
whatIs(new WeakMap());  // => "WeakMap"
whatIs(new Set());      // => "Set"
whatIs(new WeakSet());  // => "WeakSet"

whatIs(new Date());  // => "Date"
whatIs(Math);        // => "Math"
whatIs(/\d{4}/);     // => "RegExp"

whatIs(new Error());  // => "Error"

# null

nullは予約語で書き換えできないのでそのまま等価演算子で比較する。

const isNull = arg => arg === null;
console.log(isNull(null));  // => true

# undefined

グローバルのundefinedは ES5.1 以降書き換え不可になったが、ローカルスコープ内では値を代入できてしまう。
そのため、常にグローバル変数undefinedを返すvoid 0と等価演算子で比較する。

もしくは上述のtypeof演算子を用いてもよい。

詳細はundefinedについてを参照。

# 配列

配列を判定するには ES5.1以降 で提供されているArrayオブジェクトの静的メソッドArray.isArray()を利用する。

Array.isArray([]);           // => true
Array.isArray(new Array());  // => true
Array.isArray({});           // => false
Array.isArray('array');      // => false

# NaN

# isNan()

グローバルスコープに定義されたisNaN()は、数値への暗黙的な変換を試みた後にNaNかどうか (数値でないか) を判定しており、isNaN(arg)Number.isNaN(Number(arg))は同義である。

NaNかどうか調べる関数というよりは数値への変換が不可能かを調べる関数であるため、NaNの厳密な判定には使用できない。

isNaN(NaN);         // Numer.isNaN(NaN)  => true
isNaN(Number.NaN);  // Numer.isNaN(NaN)  => true
isNaN(0 / 0);       // Numer.isNaN(NaN)  => true

isNaN(123);         // Numer.isNaN(123)  => false
isNaN('123');       // Numer.isNaN(123)  => false
isNaN('');          // Numer.isNaN(0)    => false
isNaN(null);        // Numer.isNaN(0)    => false
isNaN(false);       // Numer.isNaN(0)    => false
isNaN(true);        // Numer.isNaN(1)    => false
isNaN([]);          // Numer.isNaN(0)    => false

isNaN(undefined);   // Number.isNaN(NaN) => true
isNaN({});          // Number.isNaN(NaN) => true
isNaN('hoge');      // Number.isNaN(NaN) => true

空文字列''nullfalse[]などはNumber()で変換すると 0 になるが、undefinedNaNになる。

# Number.isNaN()

NaNの厳密な判定を行うには、Numberオブジェクトの静的メソッドとして ES6 以降で提供されているNumber.isNaN()メソッドを使用する。

値がNaNで、かつ数値型である場合にのみtrueを返す。

Number.isNaN(NaN);         // true
Number.isNaN(Number.NaN);  // true
Number.isNaN(0 / 0);       // true

Number.isNaN(undefined);   // false
Number.isNaN({});          // false
Number.isNaN('hoge');      // false

ES6+ が動作しないブラウザでは以下のポリフィルを使う。
NaNは唯一自分自身と等しくないという性質を用いている。

Number.isNaN = Number.isNaN || function (arg) {
  return typeof arg === 'number' && arg !== arg;
}

# まとめ

  • undefined
    • 等価演算子===void 0と比較
    • typeof演算子で"undefined"と比較
    • 詳しくはundefinedについてを参照
  • null
    • 等価演算子===nullと比較
  • 配列
    • Array.isArray()
  • NaN
    • Number.isNaN()
    • 自分自身と比較
  • その他
    • Object.prototype.toString.call()
    • Symbol, Boolean, Function - typeof演算子

# 参考

Understanding JavaScript types and reliable type checking | Ultimate Courses™
Number.isNaN() - JavaScript | MDN

Last Updated: 2019-11-27 1:19