Javascript 豆知識。
というか、仕事で重要になった些細なこと。
演算子には評価順がある。
プログラマなら、だれでも意識することだ。
掛け算 * は、足し算 + よりも先に評価される。
でも、括弧 ( ) をつけると、それが最優先で評価される。
1 + 2 * 3 は 2 * 3 が先に評価されて 7 になるけど、
( 1 + 2 ) * 3 なら、1 + 2 が先に評価されて 9 になる、というようなこと。
まぁ、評価順が不安なら括弧で括っとけ、という話でもある。
じゃぁ、次。
6/3/2 という式があった場合、結果は何になるだろう?
これは、6/(3/2) なのか、(6/3)/2 なのか、という問題だ。
答えは、1 だ。(6/3)/2 になっている。
つまり、評価は左のものが先に行われ、右に進む。
Javascript では、累乗は ** で表される。
2 ** 3 ** 2 はどうなるだろう?
答えは 512 だ。2 ** (3 ** 2) が行われている。割り算の時とは逆で、右から順に評価される。
こうした「同じ演算子が続く場合の評価順」のことを、結合性という。
結合性は、演算子により異なる。
さて、本題。
Javascript では、「代入」も演算結果を持つ。
a = 5 という代入式があった場合、これ全体は、代入された値である 5 が結果になる。
そして、これは右から左に評価される。
a = b = 5 という式では、まず b = 5 が評価され、この結果である 5 が a に代入される。
では、次の式はどうか。
var a=0, b=[];
b[a++] = a++;
念のため書いておくと、a++ というのは「a の内容を値とした後で、a を1増加させる」という演算子だ。
a が 0 の時に a++ と書くと、その式自体は 0 なのだが、その後に a は 1 になっている。
さて、代入式は右から評価されるのだから、まず a++ が実行される。
そして、その結果が b[a++] に代入される。
…と、そう思っていた。
でも、結果は [1] になる。配列の 0 番目に、1 が入っている、という状態だ。
先に b[a++] が評価され、その後で a++ が評価されている。
ちなみに、こんな風に式の中に a++ とかを書くのはお勧めしない。
ここではわかりやすい形で書いているけど、実際には await を使った式の評価でバグが出た。
こんな感じだった。
b[a] = await func();
if(!b[a]) return;
func からの戻り値がない場合、それ以降の処理をしない、というようなプログラムだったのだが、実際には func が値を戻したとしても、return されてしまう。
書いたのは僕ではないのだけど、うまく動かないので相談が来た。
await は javascript に並列実行を引き起こすのだけど、その中で a が書き換えられていた。
それにしても、戻り値は b[a] に入ってすぐ次の行で見ているはずで、納得がいかない。
…と、この時点では思った。先に書いたように、左辺の評価は後になると思っていたからだ。
でも、左辺が先に評価されると考えないとつじつまが合わない。
そこで先のプログラムを書き、左辺の評価が先だと知ったわけだ。
Javascript でわからないことがあったら MDN を見ろ、と僕は思っているのだけど、今この記事を書くために確認していたら、ちゃんと書いてあった。
でも、長い文章の中にさらりと入っているので、気づいてなかった。
演算子の優先順位のページに書いてある。
(どこに書いてあるのか探してみよう。見落としていたのに納得してもらえると思う)
・興味深いのは、結合性や優先順位に関係なく、評価の順序は常に左から右になることです。
「計算」には優先順位があるが、「評価」は常に左から。覚えておこう。
同じテーマの日記(最近の一覧)
別年同日の日記
申し訳ありませんが、現在意見投稿をできない状態にしています。 |