スポンサーリンク

JavaScriptの動かないコード (中級編) jsonオブジェクトをevalできないエラー


以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)

<input type="button" value="「1」と表示" onClick="f()">

<script language="JavaScript">

function f()
{
	var str = "{x:1}";

	// JSONオブジェクトにする
	var obj = eval(str);

	// プロパティを表示
	alert( obj.x );
}

</script>




答え



IEでもFirefoxでも,「undefined」と表示されてしまう。



もしもオブジェクトのプロパティが1個ではなく,複数個だったらどうなるか。

	var str = "{x:1, y:2}";

これをeval()すると,undefinedではなく,実行エラーになる。


Firebugのコンソールには「invalid label」と出る。

IEのエラーダイアログでは「Webページに問題があります… ';'がありません。」と言われる。




なぜエラーになるのか。

その理由は,JavaScriptで「ラベル文」(labeled statement)を使った事があればわかるだろう。

ラベル文とは次のようなもの。

// forループに L1 という名前を付ける
L1: for( i = 0; i < 10; i ++ )
{
	if( i == 3 )
	{
		// L1という名前のループを抜ける
		break L1;
	}
	alert( i );
}

この場合,break を使って for 文を強制終了させているため,「0, 1, 2」 が表示された時点でforループが終了となる。

もしも break ではなく continueとすると,該当ループの実行が「その回だけスキップ」されるので,表示は「0, 1, 2, 4, 5, 6, 7, 8, 9」となる。(3がない)


これを理解すると,次のようなコードが書けることがわかる。

	eval("L : for(;0;){}");

何もしないループにラベルを付けている。

さらに,

	eval("{L : for(;0;){}}");

これもOK。単に前者を中括弧 {} でくくってブロックにしただけ。


{} でくくったとは言え,クロージャにしたわけではないので,特に意味・効果はない。

JavaScriptでの中括弧 {} の3つの用法
http://d.hatena.ne.jp/chaichanPaPa/20...


つまり何が問題だったかと言うと,「eval("x:〜")」の〜の部分には,「値」ではなく「文」が期待されていたということだ。

例えば

	eval("{x : alert(1);}");

このコードはエラーがなく,ふつうに「1」とアラート表示される。
ただし,ここでは x の役割はプロパティ名ではなく「ラベル」となる。


alert(1); があるべき所に,単なる「1」などの数値を入れてしまうと,

	{x:1}

は,なんと「ラベル付きの文が中括弧でくくられている」とみなされ,

	obj = eval( "{x:1}" );

の結果,objの値に数値1が代入される。

したがって,冒頭のコードでは alert( obj ); とすれば「1」と表示される。


プロパティが複数ある場合には,

	{x:1, y:2}

の最初の x: のあとの部分が「文」にならないので,IEのダイアログにあったように「セミコロンがありません」というエラーになる。



一つの解決法は,丸括弧 () でくくること。

冒頭のコードで

	var str = "({x:1})";

とすれば,evalで評価した時にハッシュとして正常に解釈される。



丸括弧によって「中身はオブジェクトです」という意思表示ができるのである。

例えばトリッキーな例だが,「0」という数値は

	(0)

のように()で囲めば,立派なNumberオブジェクトである。したがって,

	(0).constructor.constructor("alert('Hello');");

と書くと,「Hello」と表示される。

なぜなら, Numberオブジェクトのコンストラクタである Number() という関数オブジェクトの,さらにコンストラクタが,Function() だから。

元ネタ:

予約語なしにJavaScriptでいろいろしてみる
http://d.hatena.ne.jp/hoshikuzu/20080...

丸括弧一つといえ侮れない。





補足

丸括弧が「オブジェクトであることの明示」をしてくれるという事について,詳細を補足。


まず,数値から直接メソッドを呼ぶことはできない。

1.toString() // Syntax Error


しかし,丸括弧でくくればOK。

(1).toString() // "1"


それだけでなく,「変数に代入してから」という方法でもOK。

var x = 1;

x.toString() // "1"


こうなる理由は2つ。

  • ドット,つまりプロパティアクセス演算子は,ToObject()という内部関数を呼び出す。
    • だから基本型の数値は,Numberオブジェクトに一時的に変換される。それでメソッドが呼び出せるようになる。
  • 数値リテラルにドットをつなげると,小数点とみなされてしまう。

数値リテラルに直接メソッドを呼べない理由
http://d.hatena.ne.jp/paulownia/20090...

  • "( Expression )" は Expression の評価の結果を返す「グループ化演算子」。
  • "." はDecimalDegit に含まれる文字であるため、メソッド呼び出しの為のために "." を記述することはできない。


これで,丸括弧が必要な理由がわかる。

  • 数値から直接メソッドを呼ぶことは可能だが,
  • 数値.メソッド という記法では小数点とみなされてしまいそれができない。
  • 字句解析の際に数値リテラルの終端を指示するために,「)」があれば,そのあとに .メソッド と書ける。