2008-05-24

JavaScriptにおける未定義値の判別方法

前々からJavaScriptの書籍やライブラリ、他人のブログを見て気になっている事の一つに未定義値の判別があります。変数の中身が未定義値かどうかを確認する方法は下記したようにいくつかあり、一体どれが最良なのかを考えてみました
v === undefined;
typeof v === "undefined";
v === void 0;

結果を先に述べると最後の判別方法が最良だという結論に至りました。

一つ目の方法はundefined定数を使った判別です。ActionScriptのundefined定数から来ているようで、ECMA-262 3rdではundefinedは予約語でも定数でもないので(※)、この方法はグローバルオブジェクトを汚染してしまっています。ローカル変数に定義して判別に使うのは問題ありません(2008/5/25 訂正:undefinedはECMA-262 3rdやJavaScript 1.3にGlobalオブジェクトの値プロパティとして定義されていました。もっとちゃんと調べないと。反省)。jQueryはこの判別方法を使っているので知らずに書き換えると予期しない動作をします…。
(※ES 4でも予約語でない模様

二つ目は昔からあり、標準仕様で定められたtypeof演算子を使った判別で一つ目と同じく可読性もあり、良い方法だと思います。しかし、パフォーマンスと記述量では三つ中最低です。

三つ目も昔からあり、同じく標準仕様で定められたvoid演算子を使った判別でvoid演算子を知らない人を対象にすると可読性は他の二つには劣りますが、パフォーマンスと記述量のバランスでは最良です。

以下に三つの方法を各ブラウザで1000万回実行したコストを纏めてみました。単位はmsです。
判別方法IEFirefoxOperaSafari
v === undefined (ローカル変数)2030734938703
v === undefined (グローバル変数)297023129681625
typeof v === "undefined"81307359694000
v === void 02030734954672

結果は一つ目と最後の判別が総合的に最も良い結果を出しました。
他の二つの結果が悪い原因としては、二つ目はスコープを跨いだ値の探索が裏で実行されている為で、三つ目は文字列比較にコストがかかっているのだと思われます。

上述したことから、判別方法はローカル変数かvoid演算子のどちらかとなりますが、ローカル変数はその都度定義するのは煩わしいので、総合的にはvoid演算子が最良だといえます。
またvoid演算子はJavaScript不遇の時代のIE4やNN4の頃からありますし、昔の書籍にも書かれています(※)。ですので互換性も問題ないと思います。
(※HTMLのA要素のhref属性に書かれた「javascript:void(0)」などは有名なのではないでしょうか)

---- ここから蛇足&愚痴です。無視してください… --------------------------
jQueryのundefined変数による判別は謎です。作者はActionScriptからのユーザなのかな?ActionScriptの構文はJavaScriptに似ていますが、全くの別ものです。悲しい事にJavaScriptのコードはActionScriptのインタプリタであっさりエラーになります。

また新しいJavaScriptの仕様であるECMAScript 4th EditionはAdobeによる圧力なのかActionScriptに強く影響を受けていて、昔のコードが動作しなくなる事もあるようです。互換性は大切にしてほしかった。また仕様も複雑になってます。3thの仕様はシンプルでとても好きです。不足している機能はライブラリを利用すれば良いですし、個人的に今で十分なのでFirefox以外の環境に実装されないことを草葉の陰から願ってます。大規模開発なんて個人はしないのだから、ECMAとブラウザ各社で調整して別の言語を搭載すればよいのに・・・。



<追記>(2008/5/25)
jQuery以外のライブラリはどうなのか気になったのでprototype.jsとExtのソースを見たところ、prototype.jsは殆どがundefined変数で判別しており、Extはtypeof演算子とundefined変数がごっちゃでした。興味深いことにvoid演算子による判別はどちらも全くしていません。

手元にあった下記のJSライブラリを'void(0)'と'void 0'でgrepしたのですが、'void(0)'でヒットしたのは殆どがHTMLファイルで、アンカーのhref属性に設定されている"javascript:void(0)"でした。JavaScriptでヒットしたのもありましたが、それもhref属性に設定するコードの末尾に戻り値を返す為に使っているぐらいです。
'void 0'は国産のConcurrent.Threadで見つかったぐらいです(こちらはちゃんと判別に使っていました)。
--------------------------
Ajax IME
Ajile
AutoSuggest
Behaviour
Concurrent.Thread
DWR
Dimensions
Dojo
env
Ext
LoJAX
Log4js
MochiKit
Narrative JavaScript
OpenAjaxHub
OpenMocha
Rico
Spry
SyntaxHighlighter
YUI
dowry
jQuery
json
moo tools
prototype
ruby.js
script.aculo.us
--------------------------

void演算子を判別に使うのはあまり一般的ではないのかもしれません。書籍などの影響でvoid演算子はアンカーのhref属性だけで使いものという変な慣習でもあるのでしょうか。私はvoid演算子派なので何か理由があるのではないかと不安になってしまいます。

4 件のコメント:

nakajiman さんのコメント...

私は、これ何?と聞かれないためにも、可読性を重んじた typeof 派ですね。この表現だと、その意味を説明する必要がないというのが理由です。

以前から私も気になっていたのですが、jQuery の window.undefined その存在は気になってました。jQuery は仕様としての undefined ではなく、ランタイム上の undefined にしたがって振る舞う結果主義なのだと思います。とはいっても、window.undefined を使っていないコードもあったりしますね。

どうしても気になるなら jQuery Dev で聞いてみてもいいっすよ。

aquilegia さんのコメント...

記事に追記したのですが、他のライブラリもvoid演算子で比較していませんでした。jQueryの昔のバージョン(※)ではwindow.undefinedの初期化をvoid演算子ではなく「window.undefined = window.undefined」の形でやっていましたし、nakajimanさんの仰る通りランタイム上のundefinedに従っているのかもしれません。

少し気になるので、お手数でなければいつでもかまいませんのでjQuery Devで聞いてもらえると嬉しいです。

※1.1.2で確認。1.2.1では無くなっていました。

aquilegia さんのコメント...

理由がわかりました。
私の確認ミスでECMA-262 3rdに仕様としてありました><。GlobalオブジェクトにInfinityやNaNと並んでundefinedも定義されています。JavaScript 1.3にも同じように定義されています。ですのでグローバルオブジェクトは汚染していません。記事にも破線つけておきます。

もっとよく仕様書をみないと・・・反省。

nakajiman さんのコメント...

jQuery dev に聞くまでもなく、過去に関連スレッドがありました。いにしえの window.undefined ... jQuery の場合 に window.undefined となった経緯をまとめてみました。