ラベル JavaScript の投稿を表示しています。 すべての投稿を表示
ラベル JavaScript の投稿を表示しています。 すべての投稿を表示

2009-04-06

主要ブラウザのツールで document.write した JavaScript をデバッグできるか確認してみた

おはようございます。なかじまんです。

主要ブラウザのツールで eval した JavaScript をデバッグできるか に続いて document.write した JavaScript をデバッグできるか確認してみました。もとネタとした JavaScript は、次のとおりです。

<script type="text/javascript">
document.write(
' <script> ',
' function d2() { \n',
' var foo = 1; \n',
' var bar = 2; \n',
' foo += bar; \n',
' console.log(foo, bar); \n',
' }; ',
' <\/script> '
);
</script>
<input type="button" value="d2" onclick="d2();" />

IE8

標準の開発ツールでデバッグできませんでした。何か方法をご存知でしたら教えてください。

IE7 以前

調べていません。ご存知でしたら教えてください。

Firefox 3

Firebug を使ってデバッグできますが、実用レベルではありませんでした。JavaScript 中から debugger; を使ってデバッガを起動し、ステップ実行できるのですが、ソースコードと実行中の行がチグハグで、どの箇所をデバッグしているか判断できません。何か方法をご存知でしたら教えてください。

Firefox 2 以前

Firefox 3 と同じです。

Chrome

次のとおり、標準の JavaScript Debugger を使って、デバッグできませんでした。関数単位でブレークポイントは指定できましたが、行単位はできませんでした。具体的なファイル名が分かれば、行単位でデバッグできるのですが、document.write で生成した無名リソースは、関数が最小単位のようです。何か方法をご存知でしたら教えてください。



Safari 4

標準の Web Inspector を使ってデバッグできませんでした。何か方法をご存知でしたら教えてください。

Safari 3 以前

Safari 4 と同じです。

Opera 9.5, 9.6

次のとおり、標準の Dragonfly を使ってデバッグできました。



Opera 9.2 以前

調べていません。ご存知でしたら教えてください。

以上です。document.write した JavaScript をデバッグするときは Opera 9.5, 9.6 を使うとよさそうです。本当に困り果てたときは Firebug でガンバルという選択もありそうです。

2009-04-04

主要ブラウザのツールで eval した JavaScript をデバッグできるか確認してみた

こんにちは。なかじまんです。Twitter での言いだしっぺということもあり、少し泥臭いことしました。

主要ブラウザのツールで eval した JavaScript をデバッグできるか確認してみました。もとネタとした JavaScript は、次のとおりです。

<script type="text/javascript">
eval(
' function d1() { \n' +
' var foo = 1; \n' +
' var bar = 2; \n' +
' foo += bar; \n' +
' console.log(foo, bar); \n' +
' }; '
);
</script>
<input type="button" value="d1" onclick="d1();" />

IE8

次のとおり、標準の開発者ツールを使って、デバッグできました。



IE7 以前

デバッグできませんでした。アドインが存在するかもしれませんが、調べていません。ご存知でしたら教えてください。

Firefox 3

次のとおり、Firebug を使って、デバッグできました。ただ、タイミングなどによって、うまく動作しないことがあります。何かコツがあるのかもしれません。



Firefox 2 以前

Firebug を使っても、デバッグできませんでした。JavaScript 中から debugger; を使って、デバッガを起動できるのですが、eval した JavaScript を行単位でデバッグできませんでした。また、他のアドオンが存在するかもしれませんが、調べていません。ご存知でしたら教えてください。

Chrome

次のとおり、標準の JavaScript Debugger を使って、デバッグできませんでした。関数単位でブレークポイントは指定できましたが、行単位はできませんでした。具体的なファイル名が分かれば、行単位でデバッグできるのですが、無名リソースは、関数が最小単位のようです。無名リソースで、行単位にデバッグする方法をご存知でしたら教えてください。



Safari 4

次のとおり、標準の Web Inspector を使って、デバッグできました。



Safari 3 以前

デバッグできませんでした。アドオンが存在するかもしれませんが、調べていません。ご存知でしたら教えてください。

Opera 9.5, 9.6

次のとおり、標準の Dragonfly を使って、デバッグできました。



Opera 9.2 以前

調べていません。ご存知でしたら教えてください。

以上です。最新か次世代のブラウザであれば、おおむねデバッグできるようですね。

2008-08-02

Permission denied to get property Object.constructor

Firefox 3.0にFirebugアドオン(1.2.0b7)を入れたところ、JavaScriptを使ったページで次のエラーが表示されました。FirebugがFirebugを無効にすると直ります。
Permission denied to get property Object.constructor

Firebugは独自にプロパティのアクセス制御をしているのですね。便利ですが使う際に注意が必要そう。

2008-06-06

FirefoxのsetIntervalに不具合?

FirefoxのsetIntervalは複数を一度に登録して実行するとまとめて処理してしまいます。次のコードは1秒間隔で3回、テキストエリアに実行回数と時間(秒.ミリ秒)を出力する関数を定義して、それを10回呼び出しています。
// 関数を定義
var textarea = document.f.ta;
var fn = function(){
var count = 0;
var id = window.setInterval(function(){
count++;
var d = new Date();
textarea.value += count + " : " + d.getSeconds()
+ "." + d.getMilliseconds() + "\n";
if(count > 2){
clearInterval(id);
}
}, 1000);
};

// 10回呼び出す
for(var i=0; i<10; i++){
fn();
}

これを実行するとFirefox以外は一秒毎に3回10行づつ出力され、合計30行が出力されるます。期待する結果どおりで正しいです。それがFirefoxは一秒後に30行出力されてしまいます。
// Firefox以外
1 : 2.843
1 : 2.859
1 : 2.859
1 : 2.859
1 : 2.859
1 : 2.859
1 : 2.859
1 : 2.859
1 : 2.859
1 : 2.859
2 : 3.859
2 : 3.859
2 : 3.859
2 : 3.859
2 : 3.859
2 : 3.859
2 : 3.859
2 : 3.859
2 : 3.859
2 : 3.859
3 : 4.859
3 : 4.859
3 : 4.859
3 : 4.875
3 : 4.875
3 : 4.875
3 : 4.875
3 : 4.875
3 : 4.875
3 : 4.875

// Firefox
1 : 51.843
1 : 51.859
1 : 51.859
1 : 51.859
1 : 51.859
1 : 51.859
1 : 51.859
1 : 51.859
1 : 51.859
1 : 51.859
2 : 51.859
2 : 51.859
2 : 51.875
2 : 51.875
2 : 51.875
2 : 51.875
2 : 51.875
2 : 51.875
2 : 51.875
2 : 51.890
3 : 51.890
3 : 51.890
3 : 51.890
3 : 51.890
3 : 51.906
3 : 51.906
3 : 51.906
3 : 51.906
3 : 51.921
3 : 51.921

自分のコードが悪いのかと思ったのですが、見直しても悪く思えません。試しにMozilla 1.0とNetscape 7.1で試したところ、Mozillaは他のブラウザと同様の結果となりましたが、NetscapeはFirefoxと近い結果になりました(1回と2回が一秒後に出力。3回目は2秒後に出力)。

setTimeoutを使って同じ処理を実行したところ、こちらは問題ありませんでした。一回毎にsetTimeoutで登録しなおしているからでしょうか。

他にも例では10個同時に実行してますが、一つだけだとFirefoxも期待通りに動きます。またディレイ時間を20msづつずらすとこちらも期待通りに動くようになります。

どうも同じタイミングで動き出すような登録はよくないようです。Firefoxではディレイ時間に気をつけないと…。

それにしてもMozilla 1.0の頃は問題なかったのが最近のはダメってデグレートでは?気になります。でもBugzilla追うのは面倒なのでそっとしておこう。

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-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-13

Dreamweaver拡張機能のJavaScriptインタプリタは独特

Dreamweaver 拡張機能のJavaScriptで気づいた点をメモしておきます。

 1. 関数内でFunctionコンストラクタによって定義した関数でグローバルオブジェクトのプロパティを参照できない
 2. NN4のdocument.layersプロパティがある
 3. Array.prototype.sortに0を返すソート関数を指定して並び替えた結果が他のJavaScript実行環境と違う

一つ目はグローバルオブジェクトに定義された関数を実行する次のコードを実行すると、何故か未定義エラーとなります。他のJavaScript実行環境ではグローバルオブジェクトのプロパティを返しますし、関数をインライン定義すると期待した結果が返るのでバグなのかな。
function objectTag() {
try{
var f = new Function("return objectTag;");
f();
}catch(e){
alert(e);
}
}

二つ目はDreamweaverにはNetscape Navigator 4にあったdocument.layersプロパティがあり、同じように使うことができます。
//「<div id="aiueo" style="position: absolute;"></div>」を取得する
var layer = document.layers.aiueo;

最後は以前記事にした0を返すソート関数で並び替えるとJavaScript実行環境毎に結果が違うという話で、Dreamweaver拡張機能は他のどの実行環境とも違う結果を返しました。document.layersプロパティがあることから、NN4と互換性を持たせているかとも思いましたがNN4の並び方も独特です。この結果で実行環境の判別ができそうで笑えます。
["",void 0,null,NaN,0]    //並び替え前

[null,void 0,NaN,"",0] //Dreamweaver
[void 0,null,NaN,0,""] //NN4
[NaN,null,"",0,void 0] //Firefox
["",null,NaN,0,void 0] //IE, Opera, Rhino
[null,NaN,0,"",void 0] //Safari, AIR

上記三点から想像するに、Dreamweaver拡張機能(またはAdobe拡張機能)は独自のJavaScriptインタプリタを持っているようです。同じAdobe製であるにも関わらずAIRとDreamweaver拡張機能で振る舞いが違うのも少し残念に感じます。純Adobe製と元Macromedia製の違いからくるのかな?

2008-05-09

AIRのセキュリティ関連エラー

最近、Adobe AIRで動作するJavaScriptを書くことがありまして、その時に得た知識を投稿しておきます。AIRでJavaScriptを書いた経験のある方は常識と思いますが、AIRはセキュリティによりJavaScriptの一部機能を制限していて使用するとエラーとなります(※)。今回私が対象になったのはその中のeval関数とFunctionコンストラクタで、AIRではこの二つを使用する事ができません。どうもコードの動的生成を制限しているようです。Functionコンストラクタは関数をインライン定義(function(){...})することで大半を置換できるので問題ないですが、eval関数は代替手段がない場合が多いので困ります。

※その他の制限については下記URLに詳細が載っています。
http://livedocs.adobe.com/air/1/devappshtml/help.html?content=ProgrammingHTMLAndJavaScript_04.html#1034123

この制限への対処方法としてSandboxBridgeを使用する方法があるようです。この制限はセキュリティモデルと呼ばれており、FlashとHTMLでそれぞれ二つあります。

Flash : アプリケーションサンドボックス / 非アプリケーションサンドボックス
HTML : アプリケーションサンドボックス / クラシックサンドボックス

HTMLを例に話すとアプリケーションサンドボックスはセキュリティの厳しく、コードの動的生成などができないがAIR APIにアクセスできる。クラシックサンドボックスはセキュリティが緩く、ブラウザのようにコードの動的生成などができるがAIR APIにアクセスできないです。SandboxBridgeはクラシックサンドボックスから一部AIR APIを呼び出すものだそうで、実際は安全なアプリケーションサンドボックスで実行して、どうしてもコードの動的生成などをしたい場合はクラシックサンドボックスで実行する形にすれば良いようです。
より分かりやすい説明がウェブ上にありますので、少しリンクを貼り付けておきます。

http://blog.r-studio.jp/?itemid=62
http://weblogs.macromedia.com/akamijo/archives/2007/10/adobe_air_2_1.html

デフォルトはどちらも安全なアプリケーションサンドボックスなので、既存のライブラリをそのまま使えないことが多いですが、jQueryなど有名ライブラリが徐々にAIRへ対応しているそうなので問題はないと楽観視しています。

NN4とFirefox拡張機能のFunction#constructorプロパティ

少しマニアックなネタですが、JavaScriptでFunctionオブジェクトを生成する方法は大きく二種類あります。一つはFunctionコンストラクタを使う方法で、もう一つはインラインで関数を定義する方法です。
//Functionコンストラクタ
Function("alert('aiueo');");

//インライン定義
function(){
alert("aiueo");
}

この二つの方法でそれぞれ生成した関数のconstructorプロパティには、殆どのJavaScript実行環境でFunctionが設定されるのですが、NN4とFirefox拡張機能では違います。

NN4は関数内でインライン定義をするとconstructorプロパティにClosureが設定されます。またFirefox拡張機能(※)のインライン定義はFunctionなのですが、Functionとは違うFunctionになります。文字列表現は同じなのですが===演算子で一致しません(※)。

NN4もFirefox拡張機能もマイナーな環境ですので通常心配する必要はありませんが、もし不幸にも関わることがありましたら、この事を頭の隅に置いておくとはまらずに済む事があるかもしれません。

※overlay要素にscript要素を追加して「alert((function(){}).constructor === Function);」を実行すると簡単に確認できます。

2008-05-07

Rhinoは||演算子による代入の仕様がブラウザと少々異なる

プログラムを書くと変数に値を代入する時に値が有効なものでない場合はデフォルト値をセットすることはよくあると思います。JavaScriptではこういうケースでよく使われるテクニックとして論理和演算子(||)を使った代入があります。
例えば次の場合、関数の引数が未指定(未定義値)の場合は"default"文字列を設定します。
function aiueo(param)
var param = param || "default";
return "aiueo_" + param;
}

||演算子は値がtrueと評価できる場合は値を返し、そうでない場合は右側の式の結果を返します。条件を満たせばif文や条件演算子(?:)を使うよりも簡素に記述できるので重宝します。

ここから本題なのですが、Rhinoでは||演算子による代入の仕様がブラウザと少々異なります。例えば次のコードをブラウザで実行するとvalue変数にはnullが入るのですが、Rhinoでは未定義値(void 0)が入ります。
//全てfalseと評価される値を||演算子で繋げて、どれが代入されるか確認する
var value = NaN || false || "" || void 0 || 0 || null;

ブラウザは全てfalseと評価される値の場合は最後の値を返すのですが、Rhinoは値の型により優先度の高い値を返します。その為、ブラウザとRhino両方で動作するプログラムを書く際は留意する必要があります。参考までに優先度を下記しておきます。

void 0 - 高い
""
NaN
null
0
false - 低い

Array.prototype.lastIndexOfのドキュメント不備?

mozilla developer centorのArray.prototype.lastIndexOfページで互換性の為に示しているJavaScriptコード(「互換コード」と呼ぶ)はFirefox(SpiderMonkeyは未確認)と完全互換と書いてありますがそうではないようです。indexOfメソッドを第二引数のfromIndexにNaNなど数値ではない値を指定して呼び出すと、Firefoxは0に置き換えて検索した結果を返しますが、互換コードは配列末尾から検索した結果を返します。
var a = [1, "0", NaN, 1];
a.lastIndexOf(1, null)); //Firefox:0、互換:0
a.lastIndexOf(1, void 0)); //Firefox:0、互換:3
a.lastIndexOf(1, NaN)); //Firefox:0、互換:3

問題は互換コードは値が数値ではない場合に配列の末尾から検索しているところで、ここを0を設定して検索するようにするのと、fromIndex引数の指定有無を確認する処理を入れてあげれば結果が同じになります。
保障しませんが参考までに勝手に直したコードを下記しておきます。
if (!Array.prototype.lastIndexOf)
{
Array.prototype.lastIndexOf = function(elt /*, from*/)
{
var len = this.length;

var from = arguments.length < 2
?len - 1
:Number(arguments[1]);
if (isNaN(from))
{
from = 0;
}
else
{
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
else if (from >= len)
from = len - 1;
}

for (; from > -1; from--)
{
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
}

実装とドキュメントに差異がある時は個人的に実装を正と考えるので、ドキュメントが誤っているのだと思っています。そのうち整理されればいいなぁ。

追記
蛇足ですがSafariのlastIndexOfメソッドは互換コードと同様の振る舞いをします。クロスブラウザ対応する場合は考慮する必要があります。

2008-05-05

RegExp.prototype.testのインデックスバグ?

JSON変換の独自実装を作成したのでテストをしようと思い、他人のライブラリの結果とマッチングしてテストを行ったのですが、特定の文字がアンマッチしてしまい原因を調べたところ標題の事象を発見しました。

内容としては全ての文字で「true」と表示される次のコードを実行したところ、replaceメソッドは問題ないのですがtestメソッドがFirefoxとOpera、Rhinoでtrueとfalseを交互に出力してしまいました。
var r = "";
for(var i=0; i<256; i++){
var s = String.fromCharCode(i);
r += i.toString(16) + ":";
r += /[\x00-\xff]/g.test(s) + ":" ;
r += s.replace(/[\x00-\xff]/g,"aiueo").length + "\n";
}
alert(r);

また、直書きしているRegExpオブジェクトを変数に入れて実行したところ、FirefoxとOperaは全てtrueとなりましたが、今度はSafariがtrueとfalseを繰り返し、IEでは一つ目がtrue、二つ目以降はfalseと表示されてしまいました。

試行錯誤したところ、testメソッドを呼ぶRegExpオブジェクトをnew演算子でインスタンス化すると問題なく動作するようになりました。
var r = "";
for(var i=0; i<256; i++){
var s = String.fromCharCode(i);
r += i.toString(16) + ":";
r += new RegExp("[\\x00-\\xff]","g").test(s) + ":" ;
r += s.replace(/[\x00-\xff]/g,"aiueo").length + "\n";
}
alert(r);


原因ははっきりとわからないのですが、IEに限っては直書きすると内部で変なキャッシュが効いてしまいインデックスを共有化してしまうのではないかと思っています(IE4で同じようなバグがありますし)。他のブラウザについても似たようなものなのかなと思っています。
原因がわからないのは気持ち悪いですがtestメソッドをクロスブラウザで使う場合はその都度、new演算子でインスタンス化して呼び出す必要があるようです。ちなみにnew演算子で作成したものを変数にいれて使いまわしてもtrueとfalseが交互に表示されます。

Array.prototype.sortの並び方が違う

未定義値やnull、空文字などを含んだ配列をArray.prototype.sortに0を返すソート関数を指定して並び替えた場合、ブラウザ毎に結果が違います。
["",void 0,null,NaN,0].sort(function(){
return 0;
});

上記コードで配列を並び替えた場合、各ブラウザの結果は次のようになります。
Firefox   [NaN,null,"",0,void 0]
IE, Opera, Rhino ["",null,NaN,0,void 0]
Safari [null,NaN,0,"",void 0]

これを見る限り0を返した後に独自の判断で並び替えるようです。個人的にどれも正しい結果と思えません。結果をみてもいまいち仕様がわかりませんし、ソート関数なしの場合とも結果が違うので困ります。
対応としてはソート関数を指定する場合は、値の型も見て慎重に並び替える必要がありそうです。また、型を見るだけではまだダメで、SafariとRhinoは0とNaNの比較で0を返すと[0,NaN]としますが、他のブラウザは[NaN,0]としてしまいます。ですのでNumber型はisNaN関数でNaNかどうかを判別して順序付けをする必要があるようです。

蛇足ですがソート関数なしで並び替える場合、結果は全て同じになるのでここは問題ないです。
["",0,NaN,null,void 0]


追記<17:05>
ECMAScriptの仕様にソート関数の結果が0の場合、順序が必ずしも安定していない事が明記されていました。順序がどうなるかはJavaScript実装によるようです。従って、結果を一定にするには自分達でしっかりしたソート関数を作る必要があります。

2008-04-25

Safariの変更を追跡する

以前にSafariの特徴的な振る舞いについて記事を書いたのですが、状況が変わったようです。最近、Win版Safariの3.1.1が配布されたのをきっかけに次の二点について再確認を行ったところ、他の主要ブラウザに合せる形で修正されていました。

 1.組み込みオブジェクトの文字列表現が"[function]"となる
 2.未定義値が設定された配列インデックスはfor inやhasOwnProperty()関数で無いものとして扱われる

っで、どのバージョンで修正されたのか知りたかったので1について調べてみました。
まずSafariのJavaScript実装である、KJSを管理しているTracを眺めて、Changeset 29817のfunction_object.cpp(r29817)で修正されていることを見つけました(多分これだと思います…)。

http://trac.webkit.org/projects/webkit/changeset/29817

次にWikipediaにSafariのバージョンとビルド番号の対応表を参考に、Tracのタグを眺めたところWin版Safariはr29817を3.1から3.1.1へのバージョンアップで取り込んでいるようで、最新の3.1.1から振る舞いが変わるようです(Tracには五千番台のビルド番号もあるのですがこれはMac版かな…)。

http://ja.wikipedia.org/wiki/Safari

こういった追跡は他のプロジェクトのChangeLogやコミットコメントを眺める事になるので、書き方など色々と勉強になります。

2008-04-05

decodeURIComponent はプラス文字をデコードしない

decodeURIComponent はプラス文字をデコードしない というのをすぐに忘れてしまい、何度も何度も同じバグを作り込んでしまうのです。なので、エントリとして言語化して、ちゃんと記憶に定着させようといったところです。

次のコードを実行しても、プラス文字がデコードされず "新宿区+歌舞伎町" のままとなります。
// 新宿区+歌舞伎町
var query = '%E6%96%B0%E5%AE%BF%E5%8C%BA+%E6%AD%8C%E8%88%9E%E4%BC%8E%E7%94%BA';
alert(decodeURIComponent(query));
ですので、次のように、プラス文字を %20 に置換してからデコードするようにします。
// 新宿区+歌舞伎町
var query = '%E6%96%B0%E5%AE%BF%E5%8C%BA+%E6%AD%8C%E8%88%9E%E4%BC%8E%E7%94%BA';
alert(decodeURIComponent(query.replace(/\+/g, '%20')));
ただし、この対応の良し悪しは見解が分かれるところですので、この曖昧?な対応が気になるなら、(結局どうすればよいのかさらに迷ってしまうかもしれませんが)ネットでの解説などを読んでみてください。

少し話題がそれますが、String.replace で、複数マッチして繰り返し置換したいときは、スラッシュ表記の正規表現を使って g (グローバルマッチ) を指定するしかないと思い込んでいましたが、

次のように、第3引数に g (グローバルマッチ) を指定しても Firefox では同じ振る舞いになる とのことです。
// 新宿区+歌舞伎町
var query = '%E6%96%B0%E5%AE%BF%E5%8C%BA+%E6%AD%8C%E8%88%9E%E4%BC%8E%E7%94%BA';
alert(decodeURIComponent(query.replace('+', '%20', 'g')));
知らなかったなぁ。(雑学)

2008-02-20

JavaScriptを面白いと思った思い出

昔話になりますが社会人になる前にJavaScriptにはまった時期がありました。
そのきっかけとなったコードを思い出したので書いておきます。

alert(new new new Function("return function(){arguments.callee.prototype.toString = function(){return 'aiueo'}}"));

これを実行するとポップアップウインドウに「aiueo」と表示されます。newを三回連続でしているところに当時感動した記憶があります。98年か99年頃にネット上でこのコードに出会って「JavaScriptって面白そう」と思い、それから1年程の間JavaScriptを書いていました。
またJavaScriptは当時流行っていたJavaと違うヘンテコなオブジェクト指向(※)だったお陰で、オブジェクト指向に対して柔軟な思考を持つことができたことに今でも感謝しています。

っと、まぁどうでもいい昔話なんですが私の様にこのコードに触発される方がいれば嬉しいなぁと思う今日この頃です。

(※今だとプロトタイプベースオブジェクト指向と呼ぶようです)

2008-01-31

IEは特定条件下でoffsetTopの値がおかしくなる

JavaScriptのoffset計算について調査していたところIEのoffsetTop値がおかしいことに気づきました(Firefox、Opera、Safariは問題無し)。試しにjQueryプラグインのDimensionsでも試してみたところ、こちらも正確なoffsetTop値を算出できないようです。

内容を説明するとbody要素(青色の枠)の中に三つのdiv要素(緑、黄、赤色の枠)をネストさせたHTMLを用意します。



これらの要素には次のスタイルを適用しています。

body {
  background: #EEEEEE;
  overflow: hidden;
  border: 20px blue solid;
  margin: 10px;
  padding: 5px;
}
#d1 {
  border: 20px green solid;
  margin: 18px;
  padding: 5px;
}
#d2 {
  border: 20px yellow solid;
  margin: 10px;
  padding: 5px;
}
#d3 {
  border: 20px red solid;
  margin: 10px;
  padding: 5px;
}

この時、赤枠のdiv要素(d3)のoffsetTop値を求めたところ、88となるところが73となってしまいました。原因を調べたところ緑枠のmargin-top-width値がoffsetTopに加算されていない事がわかりました。さらに調べたところmargin-top-width値が1~15までは加算されず、16以上は加算されることが分かりました。

またこの事象はbody要素と緑枠のdiv要素の間にp要素を入れても発生するのですが、table要素(下記イメージ)を入れると今度は正しい値を返します。事象が発生する要素とそうでない要素のパターンはまだ整理できていない状態です。



IEの内部ではoffsetTop値の算出にどんなことをしているのか気になる今日この頃です。