今更その話題かよ!と思いますが、自分のためのメモとして残しておきます。
#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"
になります。
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 何らかの型]
という文字列を取得できて、そこからそのオブジェクトの型がわかるというのもご存知だと思います。
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以下では使えません。
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
になる場合があります。
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)でしか使えません。
Number.isNaN(NaN); // true
Number.isNaN(undefined); // false
Number.isNaN({}); // false
Number.isNaN('たべないよー!'); // false
代替(ポリフィル)として以下が使えます。
Number.isNaN = Number.isNaN || function (obj) {
// typeof NaN === 'number' -> true と、
// NaN !== NaN -> true を利用する
return typeof obj === 'number' && obj !== obj;
}
##関数とアロー関数
ECMAScript 2015 からアロー関数 () => {}
が使用できるようになりましたが、普通の関数との違いとして 'this' がグローバルなどを指し、'arguments' が取得できない、'prototype' を持たないなど違いがあります。
これの判定も、その性質を利用すれば簡単ですね。(実際にこういうことはしないとは思いますが)
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
も実は厄介です。
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は切り捨ててしまえとは思いますが、それができない場合はこんな方法があります。
function isWindow(obj) {
return !!obj && obj === obj.window;
}
isWindow(window); // true
window
に window.window
があるのを利用したものです。
##DOM
document
やHTML要素などのノードを判定するには Object.prototype.toString.call()
を使用するより Node.nodeType
で行えます。
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
演算子である程度判定できます。
var
paragraph01 = $('.entry p'),
paragraph02 = document.querySelectorAll('.entry p'),
obj = {};
(paragraph01 instanceof jQuery); // true
(paragraph02 instanceof jQuery); // false
(obj instanceof jQuery); // false