導入
undefinedの判定にしか使えないと言われるtypeof演算子が、もしかしたら変数にin演算子が使えるかを判定するのにも使えるのでは?
そう思い、この記事を投稿します。
[追記]結局使えなかった
@think49さんのコメントにより、結局typeof演算子はin演算子の利用可能判定に使えませんでした。
正確に言えば、undefinedの判定として使ってはいますが……私の意図した「undefinedの判定以外の用途」としては使っていません。
前提事項
この記事は、クライアントサイドのJavaScript(Webブラウザで動作するJavaScript)を前提としています。
サーバサイドのJavaScriptは経験がないので、そこでの常識とは食い違った内容になっているかもしれません。
typeof演算子の悪評
JavaScriptの中~上級者であれば、typeof演算子の利用は避けるべきということはご存知だと思います。
もし知らない方は、下記ページをご覧ください。
typeof演算子(instanceofも同様です)は恐らくJavaScriptの最大の設計ミスです。完全に壊れている存在に近いものです。
instanceofはまだ限られた用途で使用できますが、typeofは本当に使用できる実用的なケースはオブジェクトの型を調べるという起こらないケース一つしかありません。(中略)
変数が定義されているかチェックしない限りは、
typeofはどんな事をしても避けるべきです。
厳密な話をすれば、typeof演算子はプリミティブ値としてのデータ型を正しく返しているわけで、JavaScript Gardenにあるように設計ミスでもなければ壊れてもいません。
typeof演算子から学ぶJavaScriptのデータ型の概念と関係する考察のまとめ - 三等兵
しかし普通の用途では、JavaScriptのプリミティブ値のデータ型は不要です。
new Date()と{}と/abc/gが同じObject型で、"foo"とnew String("foo")が違う型では、(たとえそれが正しくとも)判定として役に立ちません。
(サーバサイドは違うらしいですが…)
このために、typeof演算子の利用は避けるべきという事になっています。
typeof演算子が役に立つ唯一の機会
JavaScript Gardenにもありますが、typeof演算子が役に立つのは変数が定義されているか確認する場合、つまりundefinedかどうかを判定する場合です。
if (typeof foo !== "undefined") {
// code ...
}
上記の場合、変数fooが定義されているかを判定しています。
グローバル変数undefinedはECMAScript 5になるまで上書き可能な変数でしたし、ECMAScript 5以降でも、関数内のローカル変数ではundefinedを上書き可能なため、グローバル変数undefinedとの比較による判定では無効になる場合があります。
このため、undefinedとの比較による検証はするべきではないでしょう。
また、typeof演算子は言語構造なので、指定した変数が未定義でもエラーが出ないという強みがあります。
in演算子の利便性と問題
in演算子は、オブジェクトにプロパティがあるかどうかをプロパティ名で判定する演算子です。
if ("geolocation" in navigator) {
// code ...
}
このin演算子をオブジェクト以外に使うと…
var foo = "foo";
if ("length" in foo) {
// code ...
}
// TypeError: Cannot use 'in' operator to search for 'length' in foo
しかし、同じ文字列でも…
var foo = new String("foo");
if ("length" in foo) {
// code ...
}
本題
in演算子が利用できるのはオブジェクトです。プリミティブ値のObject型です。
よって、typeof演算子で以下のように判定することでin演算子が利用できる変数なのかを判定できます。
if (foo !== null && typeof foo === "object") {
if ("length" in foo) {
// code ...
}
}
注意点として、nullもObject型として判定されるため、nullを完全一致で条件から除いています。
nullをObject型と判定するのは、本当の設計ミスじゃないだろうか…
追記
@think49さんのコメント曰く、私のコードではObject (host and does not implement [[Call]])やfunction(){}がプリミティブ値のObject型として判定されないとのこと。
確かに
var foo = function () { };
console.log(!!(foo !== null && typeof foo === "object"));
// false
console.log("length" in foo);
// true
function(){}はin演算子使えますが、falseと判定されてしまっていますね。
正しい方法は以下になります。
if (foo !== null && typeof foo !== "undefined" && Object(foo) === foo) {
if ("length" in foo) {
// code ...
}
}
私も自分のコードを修正してきます…
反省
眠気の影響でin演算子の利便性と問題から適当に書いてしまいました。
気が向いたら修正します。
編集リクエストも受け付けていますので、もっと最適なサンプルコードがある場合や誤った説明がある場合など、どんどん編集してください。