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 に含まれる文字であるため、メソッド呼び出しの為のために "." を記述することはできない。
これで,丸括弧が必要な理由がわかる。
- 数値から直接メソッドを呼ぶことは可能だが,
- 数値.メソッド という記法では小数点とみなされてしまいそれができない。
- 字句解析の際に数値リテラルの終端を指示するために,「)」があれば,そのあとに .メソッド と書ける。