http://www.example.com/が、以下のような内容を返すとする。
callback({ "foo" : "bar" }) ;
このとき、script要素を動的に生成することによって、same originを回避しつつ、しかもJSONデータをJavascriptから利用することが可能になる。
function callback(data) {} var jsonp = document.createElement("script") ; jsonp.setAttribute("type", "application/javascript") ; jsonp.setAttribute("src", "http://www.example.com/") ; document.head.appendChild(jsonp) ; // callback()が呼ばれる。
このテクニックは、JSONPと呼ばれている。
実際のコードでは、HTML5のdocument.headに対応してないブラウザのため、別の方法でhead要素へのDOMを取得しているが、それは話の本題ではないので、省略する。
ここまではいい。しかし、このscript要素は、無駄であるので、できれば、消したい。
document.head.appendChild(jsonp) ; // callback()が呼ばれる。 document.head.removeChild(jsonp) ;
わたしは予想では、これは動くはずであった。なぜならば、JavascriptによるDOM操作は、暗黙のうちに非同期になってくれたりはしないからだ。ひとたび、appendChild()が呼ばれたならば、DOMに追加され、読み込みが終わり、関連するすべての処理がおわるまで、Javascriptのコードは実行を続けることができないはずである。
これは、時として、ユーザーをブロックする原因にもなるが、この場合は、都合がいい仕様である。
Chrome, Safari, Firefoxでは、私の期待通りに、問題なくcallback()が呼ばれた。
しかし、Operaでは、動かなかった。
Operaでは、callback()は呼ばれるものの、その引数であるdataが、nullになっていた。removeChild()を呼び出さなければ、nullではなく、ちゃんと、JSONオブジェクトを参照しているのだ。
相変わらず、Operaの実装は分からない。最適化のために、script要素がDOMに追加された場合の、その中身の実行のタイミングを遅らせるなどの、妙なことをしているのではあるまいか。
実は、もう一つ、removeChild()を呼び出した場合の不都合が存在した。それは、ブラウザの「戻る」機能を使って、ページを戻した場合、動的に追加したscript要素内のコードが実行されないのだ。これは、すべてのブラウザに当てはまる。
結局、removeChild()を呼び出すのは、諦める事にした。script要素が存在することで、CPU時間やメモリなどのリソースの消費が、目に見えるほど増えることはない。
昔から appendChild した script が同期というのは保証されてなかったと記憶しています。
ReplyDelete試してみると、Firefox ではいつも同期、Safari と Chrome では src がキャッシュされてる場合は同期で、そうじゃない場合は非同期 (つまり removeChild した場合は callback が呼ばれない) で、Opera はいつも非同期みたいでした。
IE 以外では script.onload が使えます。
http://d.hatena.ne.jp/os0x/20080827/1219815828
余談ですが、document.write にもおもしろい話があります。
http://webreflection.blogspot.com/2009/12/documentwriteshenanigans.html
そうか、onloadを使えばよかったのか。
ReplyDeleteさっそく改良。
いまどきdocument.writeがどうしたと思ったら、これはすばらしい。
ReplyDelete