スポンサーリンク

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