Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

演算子ノ全テ

JavaScript の演算子とは何者でどういう挙動を取るのか、演算子の全て話します。 2015/7/20 追記: JavaScript 演算子の基礎知識としてQiitaにも記事を投稿しました。内容はほぼ同じです。

演算子とは

演算子 とは演算をするものです。演算子が取る値を 被演算子(オペランド) と言います。 2項演算子ならば左辺右辺とオペランドを取ります。 3項演算子ならば演算子を2つ用いて左辺右辺、それとあえて呼ぶならば中辺とオペランドを取ります。

オペランドをとる個数を アリティ と言います。アリティに基づきJavaScriptの演算子は単項演算子、2項演算子、3項演算子に分類されます。

単項にしろ2項、3項にしろ 値を取り値を返す点 は共通しています。 つまり、演算子とは「幾つかの値を取りある一つの値を返すもの」です。

演算子と式

演算子 と値で構成されています(関数を呼んだ返り値はここでは値に含むとします)。 ある値をとります 。例えば a === btruefalse というbool値を、 1 + 23 という数値を取ります。 ここで注目して欲しいのは、 a = 3 も式で値をとる、ということです。 a = 33 を取ります。だから a = b = 3 という式が成り立つわけです。

インクリメント・デクリメント

演算子は値を返す、と説明しました。これでインクリメントの記法が a++++a となんで2つあるのかが分かりますね。 a++は 「aを値として返してからインクリメント」 、++aは 「インクリメントしてからaを返す」 です。

デクリメントも同様です。a--は 「aを返してからデクリメント」 、--aは 「デクリメントしてからaを返す」 です。

よくゴッチャゴチャにしている人を見かけますが、きちんとわけてください。

if と三項演算子 と && 、 ||

ifのブロック内が式文のみの時、if を 三項演算子 もしくは && 、 || を使って書き直すことができます。 ifの後ろにelseがあったら 三項演算子で 、if のみなら && で書き直すことができます。 || は何かというと、if内をnotで否定した条件で書き直せます。

&& と || について一応原理を説明しますと、 && は左辺が truthy の時、右辺を 評価して 右辺の値を返します。しかし、 falsy の時は右辺を 評価せず 左辺を返します。だから、 && の左辺が truthy の時のみ右辺を実行します。この挙動、まさに if ですよね。

対して || は左辺が truthy の時は右辺を 評価せず 左辺を返します。 左辺が falsy なら右辺を 評価して 右辺を返します。こういう原理のため、 if 内の条件式を否定して書き直せるのです。

//before
if (a === 3) {
    b = 1 + 3;
} else {
    c = 1 + 3;
}
//after
a === 3 ? (b = 1 + 3) : (c = 1 + 3);
//before
if (a === 3) {
    b = 1 + 3;
}
//after
a === 3 && ( b = 1 + 3 );
//before
if (a !== 3) {
    c = 1 + 3;
}
//after
a === 3 || (c = 1 + 3);

左結合と右結合

演算子には 左結合 のものと 右結合 のものがあります。例えば 1 - 2 - 3((1-2)-3)とはかけますが (1-(2-3)) とは書けません。これは -が右結合の演算子 だからです。

これに対し、 a = b = 3(a=(b=3)) とは書き直せますが ((a=b)=3)とは書きなおせません( a = b はbの値を返すので)。これは =が左結合の演算子 だからです。色んな所で出てくるのに全然説明がない語ですので覚えておくといいと思います。

数学の演算子は + - * /を見て分かる通りほとんどが右結合です。

ちなみに、^(べき乗)は(JavaScriptにはありませんが)実は右結合なのか左結合なのかあやふやです。言語ごとに違っていたりします。3^2^3が言語ごとにちがうというのもおかしな話ですよね。

&& と || の話

これ実は以前も記事にしたのですが、こっちにもまとめておきます。

&& と || 、下の表にも書きましたが実はBool値を返すとは 限らない んですよね。&& から順に説明します。 && は左辺がfalsyなら左辺の値を、左辺がtruthyなら右辺の値を返します。だから別にBool値を返すとは限らないんですよね。左辺右辺どちらもBool値とは限りませんし。

|| も同様です。左辺がtruthyなら右辺の値を、左辺がfalsyなら左辺の値を返します。

truthy と falsy というのは、!!x とした時 true、falseどちらになるか表したものです。 !!x が true なら x は truthy 、!!xが false なら x は falsy です。

ifの話

if (a = arguments[0]) という謎の式、見たことありますよね。これも今までの話を踏まえるとわかると思います。 a = arguments[0] が評価されカッコの中が arguments[0] となるのです。そしてカッコの中が truthy ならブロック内を実行します。

今まで if (a = arguments[0]) はできるのに何で if (return) はできないんだ?と思ってた人もこれで解決ですね。 return は文であって値をとりませんから。

終わりに

全てといいながらさっくりとした中身で物足りなく感じた人もいたと思います。しかし、書きたかったことは書けたのでわたしは満足しています。完全自己満足ですね。

おまけ その1: === を使った小技

x === x でNaNかどうかの識別ができます。 NaN === NaN はfalseを返すのです。だから(x === x)===falseでxがNaNか識別できます。意味がわからないですよね。

おまけ その2: 演算子と式文を利用してコードを圧縮する方法

ここに書こうと思ったのですが、文量が増えたのでfor文 と 式文 と それに付随する できるとちょっとかっこいいテクニックという独立記事にしました。for文をこねくり回して最適化しています。

おまけ その3: == と === の違い

==より===の方が速いです。 == は内部的に === を使っているからだそうです。確か仕様書にそうかいてあったはずです(私が読んだわけじゃありませんが)。他にもいろいろと言われている通り == よりは ===を使ったほうがいいと思います。文字列を型変換するとどういう挙動をとるのかという問題は正直私も完璧には把握しきれていないので == なんか使いまくってたらそのうち地雷踏むと思います。

おまけ その4:演算子一覧

単項演算子 ! ~ + - ++ -- delete void typeof
2項演算子 + - * / % += -= *= /= %=
|| && | & ^ |= &= ^= =
>> << >>> >>= <<= >>>= > < >= <= == != === !==
in instanceof
3項演算子 ?:(これで一組です)

下の表は、演算子が受け取る値(被演算子・オペランド)と返り値の型をまとめたものです。 Operand は、括弧がある場合(右辺,左辺)という意味です。Anyと書いた部分は本来は入るべきでないものも入る可能性があります。

OperatorOperandReturn Value
.(Any, Any)Any
new(Function)
()FunctionAny
++NumberNumber
--NumberNumber
!AnyBool
~BitBit
+AnyNumber
-AnyNumber
typeofAnyString
voidAnyUndefined
deleteAnyBool
*(Number, Number)Number
/(Number, Number)Number
%(Number, Number)Number
+(Any, Any)Number
-(Any, Any)Number
<<(Bit, Bit)Bit
>>(Bit, Bit)Bit
>>>(Bit, Bit)Bit
<(Any, Any)Bool
<=(Any, Any)Bool
>(Any, Any)Bool
>=(Any, Any)Bool
in(String, Object)Bool
instanceof(Object, Function)Bool
==(Any, Any)Bool
!=(Any, Any)Bool
===(Any, Any)Bool
!==(Any, Any)Bool
&BitBit
^BitBit
|BitBit
&&(Any, Any)Any
||(Any, Any)Any
?:(Any, Any, Any)Any
=(Variable, Any)Any
+=(Variable, Any)Any
-=(Variable, Any)Any
*=(Variable, Any)Any
/=(Variable, Any)Any
%=(Variable, Any)Any
<<=(Variable, Bit)Bit
>>=(Variable, Bit)Bit
>>>=(Variable, Bit)Bit
&=(Variable, Bit)Bit
^=(Variable, Bit)Bit
|=(Variable, Bit)Bit
,(Any, Any)Any