Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
JavaScript
応用編
- トップページ
- JavaScript応用編 - JavaScriptにおける型判定について
JavaScriptにおける型判定について
みなさまどうもおはこんばんにちは。
続きましてJavaScriptにおいての型(データ型)の判定について色々と解説していきたと思います。
と、その前にお忙しい人のためにまとめ書いときましたので置いときます。
// JavaScriptの型判定用の関数です。1つ用意してあげるととっても便利です。(・ω<)テヘペロ var isType = function(x){ return (x != x)? "NaN": (x === Infinity || x === -Infinity)? "Infinity": Object.prototype.toString.call(x).slice(8, -1); }; // 実行結果はこんな感じです。 console.log(isType(1)); // Number console.log(isType(NaN)); // NaN console.log(isType(Infinity)); // Infinity console.log(isType('abc')); // String console.log(isType(true)); // Boolean console.log(isType(null)); // Null console.log(isType(undefined)); // Undefined console.log(isType(/abc/)); // RegExp console.log(isType((new Date()))); // Date console.log(isType((new Error()))); // Error console.log(isType([])); // Array console.log(isType({})); // Object console.log(isType(function(){})); // Function console.log(isType(document.querySelector("body"))); // HTMLBodyElement console.log(isType(document.getElementsByTagName("body"))); // HTMLCollection
では解説を進めていきたいと思います。
まずJavaScriptはデータの型を意識することなく色々な処理を構築することができます。変数周りの処理だと以下のような感じになりますね。
var x; // 型を気にせずになんでも『var』を使用してOKです。 x = 1; // 何を入れてもOKです。 x = true; // 型が違うもので上書きできます。 x = null; // 型が違うもので上書きできます。 y = 1; // グローバルとして扱われてOKならば『var』の記述すら必要ないです。何を入れてもOKです。 y = true; // 型が違うもので上書きできます。 y = null; // 型が違うもので上書きできます。
JavaScriptに限らず最近主流となっている言語はこのような感じで型を意識することなく、色々な処理を構築することができるわけでございますがここにちょっとした落とし穴があります。以下のサンプルを見てみて下さい。
var fn = function(x){ return x * 2; };
上記のサンプルはただ仮引数『x』の値を2倍して返すっていう超簡単な処理です。
お察しの良い方だとすぐにお分かりになるかと思いますが、この処理は一点大きな穴があったりします。そうです、仮引数『x』が通常の数値であるかどうかの判定処理がごっそり抜けちゃってますね。
var fn = function(x){ // この辺に仮引数『x』が通常の数値であるかどうかの判定処理を入れる必要があったりなかったりします。 return x * 2; };
仮引数『x』に絶対に通常の数値しか渡ってこない、というならば判定処理はいりません。ですがそうとも限らない事が多いのが現実です。
例えば上記の関数の引数に間違えて文字列『x』を渡して実行したとしましょう。そうすると文字列『x』を2倍するという訳のわからない処理が完成します。
しかもエラーになってくれればまだ良いものの、JavaScriptちゃんでは文字列を乗算すると『NaN』という値を何食わぬ顔で返してくるのでエラーにならず、パッと見でミスに気づけない可能性も高かったりします。
JavaScriptでは言語の特性上、フォーム系の要素(input[type=text]とかtextareaとか)に入力されたデータを扱うことが多いので上記のような型の判定処理の書き忘れには要注意です。入力フォームだと何でも入力できちゃったりしますからね。
というわけでJavaScriptでの型の判定方法について色々と確認していきましょう。
(´-`).。oO(JavaScriptでの型判定はかなりカオスなのでめっちゃ頑張って下さい...)
『NaN』(ナン)とは『Not a Number』の略で日本語だと『非数』と呼ばれ、JavaScriptだけでなく他の言語でも用意されている値です。
『NaN』はプログラミングにおいて『数値っぽいけど数値じゃない何か』を表現する値で、演算結果が『無限大』とか『虚数』とかの『コンピューターで表現できない数値っぽい何か』になった時に『NaN』を返すようになっていたりします。
JavaScriptでは無限大を表現する『Infinity』が用意されているので『無限大以外の表現できない何か』になった場合に『NaN』が返ってくる場合が多いです。
ちなみに『NaN』という値を中心に演算が組まれることはまずありません。今回のようにちょっとした型の判定とかに使われることが主な使い道です。
尚、『非数』という言葉は数学では出てきません。プログラミングの世界だけで使用される言葉(値)になりますね。
『Infinity』(インフィニティ)とは名前の通り『無限大』を表す値になります。こちらもJavaScriptだけでなく色々なプログラム言語で用意されている値になりますね。
JavaScriptですと『5 / 0』を実行したりすれば『Infinity』が返って来ます。そんでもって、符号を付けることができますのでそこは忘れないようにして下さい。
console.log(5 / 0); // 正の無限大なので『Infinity』と出力されます。 console.log(-5 / 0); // 負の無限大なので『-Infinity』と出力されます。
昔は『typeof演算子』等と他の処理を組み合わせて型判定をすることが多かったのですが、最近主流になっている手法として『Object.prototype.toString.call()』を使用するテクニックがあるのでこちらを紹介したいと思います。
『typeof演算子』(タイプオブえんざんし)とはJavaScriptで型を判定できるって設定の演算子です。以下のサンプルをみてください。
console.log(typeof 1);
上記のサンプルは数値『1』をオペランドとした『typeof演算子』の処理です。『typeof演算子』の記述方法は上記のように、『typeof』と記述した後に半角スペースとかを挟んで、その隣に判定したいオペランド(上記の場合だと数値『1』)を書いてあげればOKです。んでもって上記のサンプルを実行させるとこうなります。
"number"
文字列『number』が返って来ていますね。
『typeof演算子』はこんな感じで、数値だと『number』、文字列だと『string』、真偽値だと『boolean』、オブジェクトだと『object』、undefinedだと『undefined』、関数だと『function』といったように型を小文字の文字列で教えてくれる設定になってます。
しかし、実はこの『typeof演算子』は単体だとさっぱり使い物になりません。以下のサンプルを見て下さい。
console.log(typeof 1); // number console.log(typeof "hoge"); // string console.log(typeof true); // boolean console.log(typeof undefined); // undefined console.log(typeof function(){}); // function console.log(typeof []); // object console.log(typeof {}); // object console.log(typeof null); // object console.log(typeof NaN); // number console.log(typeof Infinity); // number
なんだかよく分からない結果が返って来てますね。空の添字配列、空のオブジェクト(連想配列)、『null』が『object』、『NaN』が『number』、『Infinity』が『number』になっちゃっています。『NaN』と『Infinity』は数値といえば数値かもですが、通常の数値とかなり特性が違うので一緒くたにされても困っちゃいますね。
(´-`).。oO(ちなみに『null』が『object』になっちゃっているのは初期のバグがそのまま仕様になっちゃったパターンみたいです...)
しかも、JavaScriptでは『new 演算子』を使用して数値とか文字列とかを通常とは違った形で定義できたりするのですが、その方法で定義されたデータを『typeof演算子』でチェックすると『object』という判定が下されちゃったりします。
var x = new Number(1); console.log(typeof x); // 『object』になっちゃいます。
※『ラッパーオブジェクト』については先の記事で解説をしています。
こんな感じで『typeof演算子』は「オペランドのデータ型を示す文字列を返すよ!」とはっきり公式サイト等に明記されているにも関わらず、『typeof演算子』だけで型の判定を行うことは出来ません。『typeof演算子』を使って型判定を行うならば他の処理も組み合わせる必要がありますのでご注意です。
『Object.prototype.toString.call()』の基本的な使い方は『()』の中に判定したいデータを入れてあげればOKで、そうするとデータの判定結果を文字列で教えてくれます。数値『1』を判定するとするとこういう記述になりますね。
Object.prototype.toString.call(1);
これを実行させるとこうなります。
"[object Number]"
文字列『[object Number]』が返ってきてますね。
色々なものを判定してみるとこんな感じになります。
console.log(Object.prototype.toString.call(1)); // [object Number] console.log(Object.prototype.toString.call(NaN)); // [object Number] console.log(Object.prototype.toString.call(Infinity)); // [object Number] console.log(Object.prototype.toString.call('abc')); // [object String] console.log(Object.prototype.toString.call(true)); // [object Boolean] console.log(Object.prototype.toString.call(null)); // [object Null] console.log(Object.prototype.toString.call(undefined)); // [object Undefined] console.log(Object.prototype.toString.call(/as/)); // [object RegExp] console.log(Object.prototype.toString.call((new Date()))); // [object Date] console.log(Object.prototype.toString.call((new Error()))); // [object Error] console.log(Object.prototype.toString.call([])); // [object Array] console.log(Object.prototype.toString.call({})); // [object Object] console.log(Object.prototype.toString.call(function(){})); // [object Function] console.log(Object.prototype.toString.call(document.querySelector("body"))); // [object HTMLBodyElement] console.log(Object.prototype.toString.call(document.getElementsByTagName("body"))); // [object HTMLCollection]
ある程度ちゃんと判定できてそうだなと思って頂けたと思います。
しかし、ちょいと数値『1』と『NaN』と『Infinity』に注目してみてください。全部『[object Number]』になっちゃってますね。
console.log(Object.prototype.toString.call(1)); // [object Number] console.log(Object.prototype.toString.call(NaN)); // [object Number] console.log(Object.prototype.toString.call(Infinity)); // [object Number]
『NaN』と『Infinity』は通常の数値と特性が大分違うので、使いやすい判定結果を出したいならば別にしてしまうのがベストです。というわけで『Object.prototype.toString.call()』を実行するまえに『NaN』と『Infinity』の判定を入れてしまいましょう。
まず『Infinity』の判定方法ですが、『===』を使用して比べちゃえばOKです。ただし、『Infinity』には符号が付く場合があるのでそこは間違えないようにして下さい。
if(x === Infinity || x === -Infinity){ // 変数『x』が『Infinity』かどうかを判定するとしたらこんな感じになります。 }
そして『NaN』の判定ですが、ここがちょっとややこしいです。JavaScriptにおいての『NaN』は通常の方法だと判定不可能だったりします。以下のサンプルをみてみましょう。『NaN』と『NaN』を比べています。
console.log(NaN === NaN);
これを実行させるとあら不思議。
false
『false』になっちゃいます。比較演算子を『==』に変えても『false』です。
「どういうことなんだべ...」ってなっちゃった方多いかと思います。これは『NaN』という値の本質が「数値っぽいけど表現できない何か」とされていることに起因しています。
例えば『0 / 0』と『"初音ミク" / 0』という演算を行った場合は両方共『NaN』になりますが、『0 / 0』と『"初音ミク" / 0』の演算結果が同値であるのはちょっと変ですよね。あくまでも『数値っぽいけど表現できない何か』になっただけなので、もし表現できれば違う値になっているはずです。
var x = 0 / 0, // 『NaN』が返って来ます。 y = "初音ミク" / 0; // こちらも『NaN』が返って来ます。 console.log(x === y); // 変数『x』と変数『y』は『表現できない』だけで同値ではありません。なので『false』と出力されます。
こういう理由からJavaScriptでの『NaN === NaN』は全て『false』になる、という風に設計されています。
じゃあ『NaN』かどうかを判定するためにどうすれば良いのか、というと以下の記述で判定できます。これは『NaN』同士が比較された時に『false』となることを利用したテクニックになりますね。
var x = NaN; console.log(x != x); // 『NaN』だった場合に『true』となります。『!==』でも問題なく判定できますが、この場合は少し曖昧な『!=』の方が無難かもしれません。
『NaN』の判定に、いかにも『NaN』を判定できそうな名前の『isNaN()』という関数(メソッド)があります。これ、使い物になりませんので注意です。以下が実行結果ですね。
console.log(isNaN(NaN)); // true console.log(isNaN("NaN")); // true console.log(isNaN(1)); // false console.log(isNaN(true)); // false console.log(isNaN("abc")); // true console.log(isNaN({})); // true
へんてこりんです。この結果は裏側で強制的に数値に変換してるのが原因らしいですが、『NaN』の判定に使えるかどうかというと使えません。
まだインターネットが普及し始めた頃のJavaScriptでは情報が非常に少なく、数値かどうかの判定に『typeof演算子』と『isNaN()』の合わせ技が使われてたりしましたが、今現在は非推薦という風潮になってます。
あと次のJavaScriptのバージョン(ECMAScript 6)で実装される『Number.isNaN()』というメソッドがあり、こちらはちゃんと『NaN』のみの判定ができると噂になってます。
が、『NaN』同士を比較する方法で判定できますので多分必要ありません。必要あったらごめんなさい。
var x = NaN; console.log(x != x); // 『NaN』だった場合に『true』となります。
JavaScriptの『NaN』と『Infinity』の判定方法をネットで検索してみたりすると『isFinite()』という関数(メソッド)が紹介されていることがあります。
isFinite(1); // 使い方はこんな感じです。
この関数は「『NaN』、『Infinity』の場合は『false』、他の場合は『true』を返してくれますよー」って感じの解説がされていたりするのですが、そんな結果には全くならないのでご注意下さい。公式サイトでもそんな解説がされちゃってますね。
以下がサンプルです。
console.log(isFinite(1)); // true console.log(isFinite("hoge")); // false console.log(isFinite(true)); // true console.log(isFinite(undefined)); // false console.log(isFinite(function(){})); // false console.log(isFinite(NaN)); // false console.log(isFinite(Infinity)); // false console.log(isFinite([])); // true console.log(isFinite({})); // false console.log(isFinite(null)); // true
かなりへんてこりんな判定になっているのが確認できると思います。
これは数値に変換できそうなものは見えないところで強制的に数値に変換してから処理するのが原因で起こってるらしいですが、まあ、使い物になるかと言われれば全く使い物にならなかったりします。
ちなみにより厳密な『Number.isFinite()』ってのがあり、オススメされてたりしますが、こちらはIE8以下だと動かないし相変わらず判定も妙だし、ってことでこちらも特に使うメリットはないかと思います。
console.log(Number.isFinite(1)); // true console.log(Number.isFinite("hoge")); // false console.log(Number.isFinite(true)); // false console.log(Number.isFinite(undefined)); // false console.log(Number.isFinite(function(){})); // false console.log(Number.isFinite(NaN)); // false console.log(Number.isFinite(Infinity)); // false console.log(Number.isFinite([])); // false console.log(Number.isFinite({})); // false console.log(Number.isFinite(null)); // false
しかし、『isFinite()』は『typeof演算子』とセットで使うことで数値かどうかの判定に使用できます。以下のような感じですね。昔はよく使われていた手法でした。
if(typeof x === "number" && isFinite(x)){ // 通常の数値ならば実行されます。 // ここに処理。 } var isNumber = function(x){ // 関数にするとこんな感じです。 return (typeof x === "number" && isFinite(x))? true: false; }
こちらの方法は間違いではありません。ただ先ほどの『typeof演算子』の解説の時と同じ理由で、『new Number()』で数値を定義した場合の判定は不可能です。
var isNumber = function(x){ // 関数にするとこんな感じです。 return (typeof x === "number" && isFinite(x))? true: false; } var x = new Number(1); // 通常とは違った形で数値を定義します。 isNumber(x)// 『typeof演算子』でチェックした場合の変数『x』は『object』という判定になってしまうため、この処理は『false』になっちゃいます。
『new Number()』で数値を定義される場合は非常に稀なため、ほぼ起こりえないケースですが一応覚えておいて下さい。
まとめると数値かどうかに限らず、型判定は『Object.prototype.toString.call()』を使ってしまうほうが良いかと思われます。
では上記の事を踏まえて型判定を行ってくれる関数をちょいと作ってみましょう。こんな感じになります。
// if文の場合。 var isType = function(x){ if(x != x) return "NaN"; else if(x === Infinity || x === -Infinity) return "Infinity"; else return Object.prototype.toString.call(x).slice(8, -1); }; // 三項演算子の場合。上記と処理結果は同じです。 // 多少読みづらくなりますがこういう処理はメンテナンスをする必要がないのでコード量を削減したり処理速度を上げたりする方向で構築してしまうのがオススメです。 var isType = function(x){ return (x != x)? "NaN": (x === Infinity || x === -Infinity)? "Infinity": Object.prototype.toString.call(x).slice(8, -1); };
上記の構造は簡単で、『NaN』と『Infinity』を判定したあとに『Object.prototype.toString.call()』を実行しているだけです。
ちょっとした改善として、指定した位置で文字をカットしてくれる『.slice()』を使って『Object.prototype.toString.call()』で返ってくる文字列の『[object 』と『]』を削除しています。この辺はお好みでどうぞ。
実行してみるとこんな感じの結果になりますね。
console.log(isType(1)); // Number console.log(isType(NaN)); // NaN console.log(isType(Infinity)); // Infinity console.log(isType('abc')); // String console.log(isType(true)); // Boolean console.log(isType(null)); // Null console.log(isType(undefined)); // Undefined console.log(isType(/abc/)); // RegExp console.log(isType((new Date()))); // Date console.log(isType((new Error()))); // Error console.log(isType([])); // Array console.log(isType({})); // Object console.log(isType(function(){})); // Function console.log(isType(document.querySelector("body"))); // HTMLBodyElement console.log(isType(document.getElementsByTagName("body"))); // HTMLCollection
大体のデータは型判定できているかと思います。
『Object.prototype.toString.call()』の仕組みはJavaScriptの根本のオブジェクトである『Object』というオブジェクトの『prototype』に用意されている『toString()』というメソッドを『call()』を使ってなりすましつつ実行させているわけでございますが、その根本の処理であるはずの『Object.prototype.toString』をJavaScriptではなぜか上書きすることができちゃいます。
Object.prototype.toString = function(){}; // これOKです。他の言語では必ず怒られるパターンだったりしますが、JavaScriptではなぜか怒られません。 Object.prototype.toString.call(1); // 上書きされちゃったのでいつもの動作はされません。
そんなことをするどアホ方もあんまりいないと思いますが、一応覚えておいて下さい。『Object.prototype.toString.call()』が動かなかったら上書きされちゃってる可能性が高いです。
いかがでしょうか。JavaScriptの型判定はかなりカオスなので、上記のような判定を行ってくれる関数をひとつ用意してあげると構築が大分楽になりますのでぜひ使ってみてください。
尚、この『Object.prototype.toString.call()』を使用した判定方法は色々なウェブサイトで紹介されているので、上記の関数が使いにくかったら色々探してみるのも一興です。色々なパターンが見つかると思います。もちろん自分で作っちゃうのもありです。その辺は適宜対応してしまって下さい。
というわけで以上になります。次の記事では上級者向けの論理演算子の挙動について色々とやっていきましょう。ではこの辺で失礼致します。
この記事は桜舞が執筆致しました。
著者が愛する小型哺乳類 |
桜舞 春人 Sakurama HarutoISDN時代から様々なコンテンツを制作しているちょっと髪の毛が心配な東京在住のプログラマー。生粋のロングスリーパーで、10時間以上睡眠を取らないと基本的に体調が悪い。好きなだけ寝れる生活を送るのが夢。ゲームとスポーツと音楽が大好き。誰か髪の毛を分けて下さい。 |
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。