読者です 読者をやめる 読者になる 読者になる

Opera9.2から9.5でのJavaScript周りの変更点

Opera JavaScript

結構な重要な変更があるんですが、OperaChangelogにそれらが抜けていたり、わかりにくい部分があるのでまとめてみます*1
Opera 9.5 for Windows Changelog(原文:Opera 9.5 for Windows Changelog)の補足的なエントリになります。

JSONPが非同期リクエストに修正

JSONP が Opera だと非同期処理できない - 2nd lifeの件で、Operaでも非同期リクエストが並列処理できる img-JSONP | TAKESAKO @ Yet another Cybozu LabsのようなBKも生まれていた困った動作がようやく改善されました。最速インターフェース研究会 :: OperaでJSONPを非同期リクエストするのサンプルで、タイマーが止まらないことを確認できます。(なんでChangelogにないんだ…)

Array:orEach,filter,map,every,someをサポート

Firefoxが1.5でサポートしたArrayの拡張にOperaも対応しました。 JavaScript 1.6 の新機能 | MDN
Safari3もArrayの拡張に対応しているので、IE以外の主要ブラウザはforEachに対応したことになります。nativeな実装だけあって高速です。(これもChangelogにない……)

  • forEach
    • 与えられた関数を、配列の各要素に対して一度ずつ実行します。
  • filter
    • 与えられた関数によってテストを実行し、それに合格したすべての要素からなる新しい配列を生成します。
  • map
    • 与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。
  • every
    • 与えられた関数によって実行されるテストに配列のすべての要素が合格するかどうかをテストします。
  • some
    • 与えられた関数によって実行されるテストに合格する要素が配列の中にあるかどうかをテストします。
Firefoxとの相違点

ただし、OperaSafari汎用的な簡易表記(generic shorthand)はサポートしていません。が、下記のように互換コードで対応することは可能です。

if (!Array.forEach) {
	Array.forEach = function(likeArray,func){
		Array.prototype.forEach.call(likeArray,func);
	}
}
Array.forEach(document.getElementsByTagName("head"),function(a){
	alert(a);
});
Array.forEach("str",function(a){
	alert(a);
});

※そのはずなんですが、Opera9.51で上記の後者(String)がうまく動きません。
また、JavaScript 1.8(Firefox3)でサポートされたArray:reduceArray:reduceRightもやはりサポートされていません。

window.postMessage

元々、OperaHTML5のCross-document messagingのpostMessageをdocument.postMessageとしてサポートしていましたが、仕様の改定にあわせてwindow.postMessageに修正されました。Firefox3もwindow.postMessageをサポートしており、Safariも4からサポートする予定(WebKit NightlyとSafari4 Developer Previewでは既に実装されています)です。そして、なんとIEもIE8でサポートする予定なので、将来的にクロスブラウザとなりそうなところが嬉しい限りです。
ただし、Operaではdocument.postMessageとして実装されていたときから肝心の中身が変わっていないため、Firefox,Safariの実装と異なっている部分があります。
John Resig - postMessage API Changes window.postMessage | MDNにあるように、Firefox3,Safari4では、postMessageにmessageとtargetOriginを渡し、MessageEventはdata,origin,sourceをプロパティとして持っています。
対してOpera9.5では、postMessageに渡すのはmessageのみ、MessageEventはdata,domain,uri,sourceをプロパティとして持っています。Operaも近いうちにHTML5の最新仕様にあわせて修正されると思いますが、現状は細かいところに違いがあるので注意が必要です。
Greasemonkey スクリプトとイベントで通信: Days on the MoonのMessageEventも参考にどうぞ。師曰く、「現在好評発売中の WEB+DB PRESS Vol. 45 の連載「JavaScrit + ブラウザ探検 第 2 回 気になる Firefox 3 の新機能」に詳しく載っています」

document.all を隠蔽するようになりました (document.allのクローキング)

Opera 9.5 のテストビルドが document.all のクローキングをサポート - えむもじらで触れられていた通り、Operaはdocument.allをサポートしていますが、if (document.all) {}という分岐条件として使われる場合(document.allを真偽値に変換する場合)、falseを返すようになりました。

javascript:alert(document.all);                   // [object HTMLCollection]
javascript:alert(document.all ? "ie" : "not ie"); // not ie

JavaScript 1.5 の Getters と Setters に対応


Firefox、Safari3もGetters/Settersをサポートしています。参考: IE 以外の JavaScript で getter setter が使えるようになる! - IT戦記

HTML5 の {Document,element}.getElementsByClassName をサポート

Firefox3,Safari3もgetElementsByClassNameをサポートしています。

clientWidth,clientHeightがdocument.documentElement,document.bodyのどちらでも参照可能にclientWidth,clientHeightが、標準モードではdocumentElementで参照するように変更

参考:ブラウザの表示領域のサイズを取得する方法。 - Enjoy*Study
9.50以前は標準モード・互換モードに関係なく、document.body.clientHeightで取得できましたが、Firefoxなどと同様に標準モードではdocument.documentElement.clientHeightで表示領域のサイズを取得するように変更されました。(2008/08/22に、「どちらでも」から「標準モード」に修正しました。)

JavaScript による直接の空間ナビゲーション

document.moveFocus{left,right,up,down}

javascript:document.moveFocusDown();void(0);

UserJavaScriptと組み合わせて遊べそうな機能です。
参考: Opera: Tutorial - Use Opera without a mouse

display:none の指定された画像についても onload が機能

まず前提として、Operaではdisplay:noneな画像(直接指定されていなくても、親のdisplay:noneを継承していれば)は読み込みをしない(リクエストが発生しない)という特徴があります(おそらく表示の高速化のためでしょう)。そのため、以前のバージョンではonloadイベントが発生することもありませんでした。
対して、Opera9.5ではimgにonloadが記述されている場合はdisplayに関わらず読み込みを行うように修正されました。
また、JavaScriptで動的に追加されたimgについてもdisplayに関わらず読み込みが発生します、こちらは以前からと同様の動作です。

<div style="display:none;" id="idiv">
<img src="http://f.hatena.ne.jp/images/fotolife/o/os0x/20080520/20080520194537.png?aaa" id="imgA" onload="alert(this.id)">
<img src="http://f.hatena.ne.jp/images/fotolife/o/os0x/20080520/20080520194537.png?bbb" id="imgB">
</div>
<img src="http://f.hatena.ne.jp/images/fotolife/o/os0x/20080520/20080520194537.png?ccc" id="imgC">
<script>
$("imgB").onload = $a
$("imgC").onload = $a// このScriptが実行される前にimgCの読み込みが完了していたら実行されない(キャッシュされている場合など)

var imgD = document.createElement("img");
imgD.src = "http://f.hatena.ne.jp/images/fotolife/o/os0x/20080520/20080520194537.png?ddd";
imgD.id= "imgD";
imgD.onload = $a

$("idiv").appendChild(imgD);
window.onload=function(){
	alert("window.onload");
	$("idiv").style.display = 'block';
}

function $(i){return document.getElementById(i);}
function $a(){alert(this.id);}
</script>

上記は、9.5では [imgA, (imgC, imgD, window.onload, imgB] の順でalertされます。
9.2では [(imgC, imgD, window.onload, [imgA, imgB] ] の順でalertされます。
Firefox,Safariでは4つのimgすべてでonloadが発生し、それらはwindow.onloadより前にalertされます。

window へアタッチされた load イベントは補足されず、対象において補足したイベントは破棄されます

これを読んで何のことかわかる人はいませんね。ただ、原文もわかり難いので、翻訳が悪いともいえないのですが…。(It no longer captures load events if a listener is attached to the window and firing capture events at target.)
で、何のことかというと、9.5以前はwindow.addEventListener("load",function(){/* ... */},true);のようにloadイベントをuseCaptureするとListnerが複数回*2呼ばれるという仕様でした。addEventListenerの第3引数をtrueにした場合というのがポイントで、useCaptureを有効にしてキャプチャリングフェーズでイベントを捕捉する場合は複数回リスナーが呼ばれるのが DOM 2 Events の仕様としては正しい動作です。Firefoxが仕様に沿っていないのですが、SafariFirefoxの実装にあわせてしまったので、Operaも一部それに都合を合わせることにしたようです。
参考: DOM Events とブラウザの実装: Days on the Moon
※まあ、要するに9.2xまでは下記のScriptはalertが何度も呼ばれてしまっていたんですが、9.5からは1回だけalertされるようになりましたってことです。

window.addEventListener("load",function(){alert(this);},true);

で、windowではなくdocumentにaddEventListenerすると今までと同じく複数回リスナーが呼ばれます。

document.addEventListener("load",function(){alert(this);},true);

長くなりましたが、一先ずは以上です。

追記

オブジェクトリテラルで最後に,をつけても構文エラーにならなくなった

{a: 1,b: 2,}のような最後に,のついた表記ができるようになりました。ただ、IEは構文エラーになるので、実際には使用しないほうが良いでしょう。なお、Firefox,Safariも構文エラーになりません。

*1:わかりやすくなっているかと言われると自信はないのですが…

*2:loadイベントを持った要素の数だけ、つまりimgが10個あるHTMLなら10回