JavaScriptの動かないコード (中級編) IEでスコープチェインにdocumentが補完されるように見える不思議
以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)
やりたい事:
- alertとかgetElementByIdなどの関数を,1文字の変数に代入して利用する。(つまり,ショートコーディングを試みる。)
<html> <body> <input type="button" onclick="f()" value="1文字関数のテスト"> <div id="my_div">fuga</div> <script language="JavaScript"> function f(){ var a = alert; a( "hoge" ); // 関数を1文字で利用 var g = document.getElementById; var elem = g("my_div"); // 関数を1文字で利用 alert( elem.innerHTML ); // fugaと表示 } </script> </body> </html>
答え
a()は,Firefox・IEの両方で動く。
g()は,Firefoxでは動かない。なぜかIEでは動く。
本来,期待すること
すでに下記の記事で,「変数代入時に,メソッドは元のオブジェクトと切り離される」という事を述べた。
JavaScriptの動かないコード (中級編) オブジェクトのメソッドを変数代入でコピーできない
http://language-and-engineering.hatenablog.jp/entry/20090630/p1
要するに,コピー/代入するとコンテキスト(this)が変わるので,動作結果も変わってしまう。
これを冒頭のコードに当てはめて考えてみよう。
alertは,正式にはwindow.alertである。
thisがwindowなので,alertと書けば自動的にコンテキストが補完され,this.alertすなわちwindow.alertと解釈される。
だから,a = alert; のあとで a() を呼び出しても大丈夫。
呼び出すときにもthisはwindowなので,window.alert()と書いたのと効果は同じとみなせる。
しかし,document.getElementByIdのほうはそうはいかない(はず)。
正式には window.document.getElementById なわけだが,このメソッドを親オブジェクト window.document から切り離して,いきなり getElementById としても動かない。
なぜなら,getElementByIdは「thisをdocumentにセットした状態で動作すべき関数」だからだ。
thisがwindowの状態で呼び出してもだめ。
- window.getElementById などというものは存在せず
- document.getElementById である。
だから(Firefoxでは)動かなかった。Firebugのコンソールには,下記のようなエラーメッセージが出る。
uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///〜〜.html :: f :: line 19" data: no]
じゃあ,どうしてIEでは動いたのか・・・・・・・・・?
IEの不思議
IEでは,下記のようなコードも動く。(Firefoxでは動かない。)
<html> <body> <input type="button" onclick="f()" value="1文字関数のテスト"> <script language="JavaScript"> function f(){ var w = document.write; w("hoge"); } </script> </body> </html>
これは本来ならば動かないのが正しいコードだ。
だが,getElementByIdの時と同じように,まるでIEではwindowだけでなく,documentというオブジェクトをスコープ内に自動補完してくれているかのように見える。
mywrite=document.write;が,IEでは動いてFirefoxでは動かない
http://forum.mozilla.gr.jp/cbbs.cgi?m...
でも,下記のようなものはIEでも動かない。
// 単にdocumentを省いただけ write("hoge");
納得に困る動作だ。
IEでは,document.writeやdocument.getElementByIdといったメソッドが,thisの値に非依存で動作するように実装されている,と考えるべきか。
メリット
変な動作ではあるが,メリットもある。
IEではdocumentを自動で補完してくれるので,JavaScriptのショートコーディングがしやすいという事だ。
JavaScript ショートコーディングの10のコツ
http://language-and-engineering.hatenablog.jp/entry/20081009/1223469525
(5)関数名に1文字のエイリアスを振る
組み込み済み関数の名前について
alert(1);alert(2);alert(3);
↓短縮
a=alert;a(1);a(2);a(3);
新関数を定義したのではない所がポイント。
IE限定で言えば,alertだけでなく,document.getElementByIdにも同様のテクニックが使える。
これは,ブックマークレットを短く作るときなどに重宝する。
(ブックマークレットはDOM操作がとにかく多いので・・・)
補足
今回の記事とは関係ないが,documentがスコープチェインに入るケース:
HTMLコード内にタグ属性として定義したイベントハンドラ中で,documentが補完される
http://rektunpe.sakura.ne.jp/diary/?d...
それを応用して,下記のような議論が生まれる:
document.getElementById() VS. getElementById()
http://stackoverflow.com/questions/85...