Javascript (ECMAScript) には変なところがたくさんある。
ここでは、クロージャーやプロトタイプチェーンといった大きいところではなくてもっと細かい変なところをまとめたい。
以前、自分がパーザーや制御文を実装していたためその辺りが多いかも。
ECMA 262 5th
Completion value
Completion value というのは、
//
100; // line. 1
200; // line. 2
とあった時に、line. 1を実行するとCompletion valueが100になり、line. 2を実行すると200になる、というものである。
まあ、 Perl の return を省略した場合の戻り値ようなものだ。
基本的に参照する方法もないし、言語実装の内部的なものなのでどうでもいいようなものだが、実は eval が返す値のことだったりする。
eval("return 10;"); がエラーになるのは、evalは文字列をjsファイルのトップレベルとして評価するためだが、 eval("10;"); で 10 が帰るのはプログラムを実行した最後のCompletion valueを返しているのだ。
厄介なのが、DOMのイベントのIE時代にトラディショナルなコードで、
<button onclick="false;" />
など書いて動いていた(今ではjQueryのbind, live, delegateを使う)が、これは、
// ex.
function (event) {
false;
}
と書いてあるのと同じことで、この関数の戻り値はundefinedでありfalseではない。
結局、コードがソースコードのどういう構文要素なのかを考えないと誤りに気付きにくい。
Type conversion
| Type name | Description |
| Boolean | ブール型 |
| Number | 浮動小数点型。整数型はない(実装内部にはあることが多い) |
| String | 文字列型 |
| Object | オブジェクト型 |
Javascriptでは特に型を考慮する必要はないが、内部的には上記の型がある。
// Clearly, 'a' contains a string primitive value.
var a = "string";
しかし、演算子や組み込み関数の中には内部的な型変換を呼ぶものがあるため、definePropertyなどしてあるとカオスなことができる。
今度書く
Regular Expression Literals
人間が見ても割り算やコメントと区別が付きづらいが、それはパーザーにとっても同じだったりする。
// ex.
/=/==/=/;
の実行結果は false だ。これは == の左右の正規表現オブジェクトの比較となり、同一のオブジェクトではないためこのようになる。
しかし、以下の2つはどうだろうか?
// ex.
/=//==/=/; // case 1
/=//=/=/; // case 2
これらをFirefox と Chormeで試すと、Firefoxでは両方同じエラーになるが、Chromeではそれぞれ異なる。
# result (Firefox)
>> Exception: ReferenceError: invalid assignment left-hand side
>> Exception: ReferenceError: invalid assignment left-hand side
# result (Chrome)
>> Exception: SyntaxError: Unexpected token =
>> Exception: ReferenceError: Invalid left-hand side in assignment
Firefox は、 /=//= までパーズして、 /= の左辺がオブジェクトだから代入できないということで ReferenceError を出している。一方、Chrome は /=//= までパーズして、次の@=@が受理できずに SyntaxError となっている。
どちらが正しいということはないが、case 1 の方は JS の構文として誤りなので、異なるエラーが出た方がいい気もする。パーザーのアルゴリズムには LL, LALR などあるが、その違いにより異なっているのだろう。
Automatic Semicolon Insertion
今度書く
Labelled Statements
JSには Labelled Statement というものがある。
これがどういうものかというと、
// example
foo:
for(;;) {
for(;;) {
print("hello.");
break foo;
}
print("ouch!");
}
で、内側のbreakで外のループも抜けるというものだ。これも perl から借りてきたような感じがする。 C プログラマには違和感があるし、特に使っているコードを見た覚えがない。いろいろあると思うが、関数を分けてreturnするとか、例外を投げることが多いような気がする。
実は continue でも使える。ちょっと見てみよう。
// example
foo:
for(var i = 0; i < 3; i++) {
for(;;) {
print("hello.");
continue foo;
}
print("ouch!");
}
実行すると、
bc.
hello.
hello.
hello.
となる。これくらいだったらまあ分かると思うが、もっと大きいループでこれをやられると悪夢ではないだろうか。
ところで、ちょっとした間違いで LabelledStatement になってしまって混乱することもある。一番ありそうなのは、
// example
{ a: 1 }
というJSONを括弧で囲まないまま eval して戻り値が 1 になって首をかしげることではないかと思う。
この場合、外側の { } はコードブロックを表し a というラベルが付いた 1 というステートメントが実行され、その Completion Value である 1 が返る。
Object Literal
IE6 では、 ObjectLiteral の最後にカンマがあるとパーズエラーになってしまう。
特に jQuery では options に Object Literal を渡すことが多いので気をつけたほうが良い。
こういうのはツールでチェックするべきだろう。
// example
{
foo: 100,
bar: 200, // <- Causes parse error in IE6
}
DOM
Event handler
スコープに上の要素が入る。
今度書く
The window object
今度書く

コメントする