2008-05-30

JavaScriptのprivateメソッド

Operaなどでは動きませんがJavaScriptでprivateメソッド的なものを実現してみようと思います。方法は簡単でレキシカルスコープとcallerプロパティを使います。
var a = function(){
fn();
};
var b = function(){
fn();
};
var fn = (function(accepts){
accepts = accepts || [];
return function(){
if(accepts.indexOf(arguments.callee.caller) === -1){
alert("NG");
}else{
alert("OK");
}
};
})([a]);
a();
b();

上記のコードは三つの関数を定義しています。aとbはfnを呼び出す関数で、fnは呼び出される関数です。fn関数は無名関数を呼び出すことで定義します。この時、呼び出しを許可する関数を渡すことで公開範囲を指定することができます。このコードではa関数からの呼び出しのみを許可しています。コードを実行すると「OK」と表示された後に「NG」と表示されます。OKやNGと表示している部分を書き換えて、メソッド化してあげればprivateメソッドみたいなものになると思います。

クロスブラウザが実現できないことや隠蔽したいケースは稀なことから、使い道はないと思いますが、callerプロパティのユニークな使い方ではないでしょうか。

2008-05-28

JavaScriptのプリミティブ型と参照型

ずっとマニアックなネタが続いたいたので今回は基本的なJavaScriptの型のお話。JavaScriptにはプリミティブ型と参照型があるのはご存知でしょうか。例えば次の二つの文字列は同じ"aiueo"という文字列ですが、プリミティブ型と参照型の違いがあります。
var p = "aiueo";   // プリミティブ型
var r = new String("aiueo"); // 参照型

通常、文字列を定義する場合はStringコンストラクタは使わずに直接記述すると思います。これはプリミティブ型でtypeof演算子で"string"と判別されます。またStringコンストラクタを使って定義すると参照型となり、typeof演算子で"object"と判別されます。
両者は同じようにプロパティへアクセスすることができ、普通に使う分には違いを感じないと思います。二つには違いはどこにあるのでしょうか。文字列を例に違いの一部を表にしてみました。

 プリミティブ型参照型
typeof演算子(typeof(x))"string""object"
instanceof演算子(x instanceof String)falsetrue
プロパティの定義不可可
同じ値を===演算子で比較truefalse

基本的なところで大きく違うことがわかると思います。この中で余り知られていないのはプロパティの定義ではないでしょうか。次のコードを実行すると、文字列にくっつけたプロパティが参照型の値では取得できますが、プリミティブ型ではundefinedとなります。
var p = "aiueo";
var r = new String("aiueo");
p.id = 100;
r.id = 200;

alert(p); // undefined
alert(r); // 200
alert(p.hasOwnProperty("id")); // false
alert(r.hasOwnProperty("id")); // true

JavaScriptでコーディングする際はこの違いを意識して書くとミスも減ると思います。最後にプリミティブ型と参照型を区別なく判定する小技と一つ。型判定はtypeof演算子やinstanceof演算子を使うのではなく、constructorプロパティで行うとプリミティブ型と参照型を区別なく判定できます。例を下記します。
var p = "aiueo";
var r = new String("aiueo");
alert(p.constructor === String); // true
alert(r.constructor === String); // true

2008-05-27

いにしえの window.undefined ... jQuery の場合

JavaScriptにおける未定義値の判別方法
jQueryのundefined変数による判別は謎です。undefinedはECMA-262 3rdやJavaScript 1.3にGlobalオブジェクトの値プロパティとして定義されていました。もっとちゃんと調べないと。反省)。jQueryはこの判別方法を使っているので知らずに書き換えると予期しない動作をします…。
aquilegia さんの投稿の中で、jQuery の window.undefined の実装に対して疑問があった(過去形)ようなので、その経緯を調べてみました。

その経緯は jQuery General Discussion の中にそれらしき議論がありました。どうやら、window.undefined というグローバルオブジェクトは、jQuery 1.0 の開発中に導入したものらしいです。

もともとは、undefined と判定すべき箇所が、null 判定していたことに起因するようです。
foo === null は foo === undefined の誤りでは?
ですが、undefined は IE5.0 とそれ以前では使えない ので、次のように表現し直そうとしたみたいです。
typeof foo === 'undefined' 
ですが、この表現を好まないとして、次のように window.undefined を用意したようです。
window.undefined = window.undefined;
これにより、IE 5.0 とそれ以前を含む古いブラウザでも undefined が使えるようになって、かつ ECMA-262 3rd の undefined グローバルオブジェクトの要件も満たすということです。

次のようにしても、同じ効果があるとの説明もありました。スコープの影響を受けるので、汎用ライブラリには向かないかもしれませんね。
var undefined;
で、aquilegia さんが提案するとおり、undefined の判定は void 演算子を使うのが確実そうなんですが、IE 3 まで遡ると void 演算子がない ようですね。

だとすると、jQuery の window.undefined という実装は、undefined の書き換えの不都合は抱えますが、かなり広範囲でブラウザの互換性を実現する手段となっているということになりますね。

ちょっと専門外の話題に言及したので、知識が曖昧な部分があったかもしれません。誤りがあれば aquilegia さんが訂正してくれるだろうと期待してます。

2008-05-25

jQuery Detect そのページが jQuery を使っているか検出してくれる Firefox Addon

次の 2007 年の調査からも分かるように、jQuery のシェアが地道な感じでしてきています。わたしも意識してチェックするようにしていますが、国内外も含めて jQuery を使っているサイトやサービスは明らかに増えているという実感があります。

Prototype、jQuery、Mootools、YUI、Dojoが人気、Ajaxian 調査
2007 Ajax Tools Usage SurveyはAjaxian.comのユーザを対象に実施された調査で、Ajaxツールの採用状況を調べるもの。同調査結果によるとPrototype が34.10%、jQueryが29.30%、Extが22.50%、Script.aculo.usが22.30%、Mootoolsが14.30%、 YUIが13.00%、Dojoが11.90%となっている。
そうはいうけどさ。実際にそんなに使われているの。統計の方法に偏りがあって、その内容なんて信用できないよ。というひとは、次の Firefox Addon をしばらく使ってみてはどうでしょうか。

jQuery Detect



この Addon は、そのページが jQuery を使っているか自動的に検出し、jQuery の有無とそのバージョンを、アイコンで教えてくれるものです。この Addon があれば、HTML ページのソースを解析したり、Firebug を開いたりすることなく、jQuery を使っているかどうか調べることができますね。

jQuery のディスカッションでも指摘がありますが、アイコンとそのバージョンは、最後にロードしたページの状態を表します。ので、タブを選択したとしても、そのタブのページの結果を表さないので、誤解しないように気をつけてください。

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演算子派なので何か理由があるのではないかと不安になってしまいます。

FirefoxのRegExpコンストラクタ

JavaScriptで正規表現を利用する際にRegExpオブジェクトを利用すると思います。この時パターンを指定しない場合のFirefoxの振る舞いが標準仕様に従っていません。Firefox2.0で確認したのですが次のようになります。
new RegExp().toString()  // 「/?:/」
new RegExp(void 0).toString() // 「/undefined/」

IEやOpera、Safariなどは次のようになります。
new RegExp().toString()  // 「//」
new RegExp(void 0).toString() // 「//」

どちらが正しいかというとECMA-262 3rdの仕様に従っているのは後者になります。IE4やNN4は引数に未定義値を指定すると「/undefined/」となり、Firefoxと同じになります。ですので後方互換が理由と思ったのですが、引数が未指定の場合はIE4とNN4共に「//」となり後者と一致します。ですので引数が未指定の場合の理由がよくわかりません。例外的な振る舞いなので重要視していないのかな?
標準大好きのMozillaがECMAの仕様に従っていないのは違和感を感じますし、他にもありそうで不安になります。

<追記>
RhinoはFirefoxと同じ結果になりました。RhinoはNetscape社からMozilla Foundationに譲渡された経緯もありますし、SpiderMonkeyと同じ振る舞いをするのかな。でも、Rhinoはオブジェクトのプロパティに順序性がないなど、SpiderMonkeyと同じではないので違う物として扱わないといけないのがなんとも面倒。

2008-05-21

jQuery 1.2.5 が即リリースされました。1.2.4 はビルドに不都合があったようです。

jQuery 1.2.4 は ビルドに不都合があった ようで、すぐに 1.2.5 がリリースされました。

次のとおり、リリースノートにも告知がありましたので、jQuery 1.2.4 を導入しているときは、1.2.3 に戻すか、1.2.5 にアップデートするか、いずれかの対策が必要そうです。

jQuery 1.2.4 Release Notes
Note 1.2.4 was a bad build, explanation here http://www.nabble.com/1.2.4-missing-patches--td17354452s27240.html Use 1.2.5 or later instead
数日間のことですので、対象者は少ないでしょうが、お伝えしておきます。