演算子の多くは既に学校で学んでおり、よく知られています。加算 +, 乗算 *, 減算 - などです。

このチャプターでは、学校の算数でカバーされていない側面に集中します。

用語: “単項演算子”、 “二項演算子”、 “オペランド”

次に進む前に一般的な用語を理解しましょう。

  • オペランド – は演算子が適用されるものです。たとえば、 乗算 5 * 2 では、2つのオペランドがあります: 左のオペランドは 5, 右のオペランドは 2 です。“オペランド” は “引数” と呼ばれることもあります。

  • 演算子が単一のオペランドをもつ場合は 単項演算 です。たとえば、負の単項演算 "-" は数値の符号を反転します:

    let x = 1;
    
    x = -x;
    alert( x ); // -1, 負の単項演算が適用されました
  • 演算子が2つのオペランドを持つ場合は 二項演算 です。同じマイナスも二項演算で同様に存在します:

    let x = 1, y = 3;
    alert( y - x ); // 2, 二項演算子マイナスは値を減算します

    正式には、ここでは2つの異なる演算子について話をしています。: 負の単項演算(単一のオペランド, 符号の反転) と二項演算による減算(2つのオペランド、減算)です。

文字列の連結、二項演算子 +

では、学校の算術を越えた JavaScript 演算子の特別な機能を見ていきましょう。

通常、プラス演算子 + は数値の合計です。

しかし二項演算子 + が文字列に適用された場合は、お互いの文字を結合します。

let s = "my" + "string";
alert(s); // mystring

一方のオペランドが文字列の場合、他のオペランドも文字列に変換されることに注意してください。

例:

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"

上のように、どちらのオペランドが文字列なのかは関係ありません。このルールはシンプルで、もしもどちらかのオペランドが文字列の場合、同様に他の一方も文字列に変換されます。

文字列連結や変換は二項演算子プラス "+" の特別な機能です。他の算術演算子は数値でのみ動作します。それらは常にオペランドを数値に変換します。

例えば、減算と除算です:

alert( 2 - '1' ); // 1
alert( '6' / '2' ); // 3

数値変換 単項演算子 +

プラス + は2つの形で存在します。上で使ったような二項演算の形式と単項演算の形式です。

単項演算子プラス、もしくは言い換えると単一の値に適用されるプラス演算子 + は、数値に対しては何もしません。しかし、オペランドが数値でない場合は数値に変換します。

例:

// 数値の場合、何の影響もありません
let x = 1;
alert( +x ); // 1

let y = -2;
alert( +y ); // -2

// 非数値を数値に変換します
alert( +true ); // 1
alert( +"" );   // 0

これは Number(...) と同じですが、より短い表現です。

文字列から数値への変換が必要なケースは多いです。例えば、HTMLのフォームフィールドから値を取得する場合、それらは通常文字列です。

今、それらの合計が欲しい場合はどうなるでしょう?

二項演算子プラスはそれらを文字列として結合します。:

let apples = "2";
let oranges = "3";

alert( apples + oranges ); // "23", 二項演算子プラスは文字列を結合します

数値として扱いたい場合は変換して合計します:

let apples = "2";
let oranges = "3";

// 二項演算子プラスの処理の前に、両方の値が数値に変換されます
alert( +apples + +oranges ); // 5

// 長い書き方
// alert( Number(apples) + Number(oranges) ); // 5

数学者の立場からは、余分なプラスは奇妙に見えるかもしれません。しかし、プログラマの立場からは特殊なことではありません: 単項演算子プラスが最初に適用され、文字列から数値に変換されます。次に二項演算子プラスはそれらを合計します。

なぜ二項演算子プラスの前に単項演算子プラスが適用されるのでしょうか? それは、単項演算子プラスの 優先順位が高いため です。

演算子の優先順位

式が1つ以上の演算子をもつ場合、実行順はそれらの 優先順位 により決められます。言い換えると、演算子の間には暗黙の優先順があります。

学校で学んだように、式 1 + 2 * 2 の場合、私たちは加算の前に乗算をすることを知っています。それがまさに優先順位です。乗算は加算より より高い優先順位 です。

丸括弧はどの優先順位よりも優位に立つので、もとの優先順位が不適当であれば丸括弧を使います。たとえば (1 + 2) * 2 のようにです。

JavaScriptでは多くの演算子があります。どの演算子も対応する優先順位を持っています。より大きな値をもつ演算子は最初に実行されます。同じ優先順の場合、実行順は左から右になります。

優先順位テーブルの抜粋(これを覚えておく必要はありませんが、単項演算子は対応する二項演算子よりも優先順位が高いことに留意してください):

優先順位 名前 符号
16 単項プラス +
16 単項否定 -
14 乗算 *
14 除算 /
13 加算 +
13 減算 -
3 代入 =

ご覧の通り、 “単項演算子プラス” は優先順位 16 で、 “加算” の 13 よりも大きいです(二項演算子プラス)。なので、式 "+apples + +oranges" において、単項演算子プラスは最初に動作し、次に加算が実行されます。

代入

代入 = もまた演算子であることに注意しましょう。 3というとても低い値として優先順位テーブルにリストされています。

なのでx = 2 * 2 + 1 のように変数を代入するとき、計算が最初に行われ、その後 = が評価され、 x に結果が格納されます。

let x = 2 * 2 + 1;

alert( x ); // 5

代入をチェーンすることもできます:

let a, b, c;

a = b = c = 2 + 2;

alert( a ); // 4
alert( b ); // 4
alert( c ); // 4

チェーンされた代入は右から左へ評価されます。最初に最も右の式 2 + 2 が評価され、次に左の変数に代入されます。: c, baです。 最後にすべての変数は単一の値になります。

代入演算子 "=" は値を返します

演算子は常に値を返します。それは加算 + または 乗算 * のように多くの場合明らかです。しかし代入演算子もそのルールに従います。

x = value の呼び出しは valuex に書き込み、 それを返却します

これは、式の一部に代入を利用した複雑な式のデモです:

let a = 1;
let b = 2;

let c = 3 - (a = b + 1);

alert( a ); // 3
alert( c ); // 0

上の例は、(a = b + 1) の結果は a に代入された値です(3 です)。その後、それ結果は 3 から減算するのに使われます。

サードパーティーのライブラリで見かけることがあるので、どのように動作するかは理解しておくとよいでしょう。しかし、このようなものは書くべきではありません。このようなトリックは明らかにコードの可読性を損ないます。

剰余 %

剰余演算子 % は、記号は % ですが、パーセントと関係はありません。

a % b の結果は、ab で割った余りです。

例:

alert( 5 % 2 ); // 1は5を2で割った余りです
alert( 8 % 3 ); // 2は8を3で割った余りです
alert( 6 % 3 ); // 0は6を3で割った余りです

べき乗 **

べき乗演算子 ** は最近言語に追加されました。

自然数 b において、a ** b の結果は、b の回数だけ自身 a を乗算した結果です。

例:

alert( 2 ** 2 ); // 4  (2 * 2)
alert( 2 ** 3 ); // 8  (2 * 2 * 2)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2)

演算子は非整数値の a や b でも同様に動作します。例えば:

alert( 4 ** (1/2) ); // 2 (1/2のべき乗は平方根と同じです)
alert( 8 ** (1/3) ); // 2 (1/3のべき乗は立方根と同じです)

インクリメント/デクリメント

数値を1ずつ増減する操作は、最も一般的な数値演算の1つです。

なので、そのための特別な演算子があります:

  • インクリメント ++ 変数を1増加させる:

    let counter = 2;
    counter++;      // counter = counter + 1 と同じですがより短いです
    alert( counter ); // 3
  • デクリメント -- 変数を1減少させる:

    let counter = 2;
    counter--;      // counter = counter - 1 と同じですがより短いです
    alert( counter ); // 1
重要:

インクリメント/デクリメントは変数に対してのみ適用可能です。 それを 5++ のように値に対して使おうとするとエラーになります。

演算子 ++-- は変数の前後両方に配置することができます。

  • 演算子が変数の後にある場合、それは “後置式” と呼ばれます: counter++
  • “前置式” は演算子が変数の前に来るときです: ++counter

結果はどちらも同じです: counter1 増加します。

それらに違いはあるでしょうか?はい、その違いは ++/-- の戻り値を使う場合にだけ現れます。

違いを明確にしましょう。ご存知の通り、すべての演算子は値を返します。インクリメント/デクリメントも例外ではありません。前置式は新しい値を返す一方、後置式は古い値を返します(インクリメント/デクリメントの前)。

違いを例で見てみましょう。

let counter = 1;
let a = ++counter; // (*)

alert(a); // 2

ここで (*) の行の前置呼び出し ++countercounter を増加させ、2 という新しい値を返します。そのため、 alert2 を表示します。

後置式を使いましょう:

let counter = 1;
let a = counter++; // (*) ++counter を counter++ に変更

alert(a); // 1

(*) の行で、 後置counter++counter を増加させますが、 古い 値を返します(増加する前)。そのため、 alert1 を表示します。

要約すると:

  • インクリメント/デクリメントの結果を使わない場合、どちらの形式を使っても違いはありません。:

    let counter = 0;
    counter++;
    ++counter;
    alert( counter ); // 2, 上の行は同じことをします
  • 値の増加に 加えて、すぐに演算子の結果を使いたい場合は前置式が必要になります:

    let counter = 0;
    alert( ++counter ); // 1
  • 増加させるが、以前の値を使いたい場合は後置式が必要です:

    let counter = 0;
    alert( counter++ ); // 0
他の演算子の中でのインクリメント/デクリメント

演算子 ++/-- は同様に式の中でも使うことができます。それらの優先順位は他の算術演算子よりも高いです。

例:

let counter = 1;
alert( 2 * ++counter ); // 4

比較:

let counter = 1;
alert( 2 * counter++ ); // 2, counter++ は "古い" 値を返すからです

技術的には問題ありませんが、このような記法は一般的にコードの可読性を下げます。1行で複数のことを行う – よいことではありません。

コードを読むとき、上から読んでいく “縦の” 目視はこのような counter++ を見逃しやすく、また変数の増加が明白ではありません。

“1行は1アクション” のスタイルが推奨されます:

let counter = 1;
alert( 2 * counter );
counter++;

ビット演算子

ビット演算子は引数を 32ビットの整数値として扱い、それらのバイナリ表現のレベルで処理します。

これらの演算子はJavaScript固有のものではありません。多くのプログラミング言語でサポートされています。

演算子のリスト:

  • AND ( & )
  • OR ( | )
  • XOR ( ^ )
  • NOT ( ~ )
  • LEFT SHIFT ( << )
  • RIGHT SHIFT ( >> )
  • ZERO-FILL RIGHT SHIFT ( >>> )

これらの演算子はめったに使われません。それらを理解するためには、低レベルの数値表現について掘り下げるべきであり、それは現時点では最適ではないでしょう。すぐには必要ないからです。もし興味がある場合は、MDNのビット演算子 の記事を参照してください。実際に必要になったときにそれをするのが現実的でしょう。

その場で変更する

しばしば変数に演算子を適用し、その中に新しい値を格納する必要があります。

例:

let n = 2;
n = n + 5;
n = n * 2;

この表記法は演算子 +=*= を使うことで短縮することができます:

let n = 2;
n += 5; // 今 n = 7 (n = n + 5 と同じ)
n *= 2; // 今 n = 14 (n = n * 2 と同じ)

alert( n ); // 14

短い “変更と代入” 演算子はすべての算術演算子とビット演算子に存在します。: /=, -= など。

このような演算子は通常の代入と同じ優先順位を持っています。そのため、ほとんどの他の計算の後に実行されます:

let n = 2;

n *= 3 + 5;

alert( n ); // 16  (右のパートが最初に評価されるので、 n *= 8 と同じです)

カンマ

カンマ演算子 ',' は最もレアで普通ではない演算子の1つです。より短いコード書くために使われることがあるので、何が起こっているのか理解するために知っておく必要があります。

カンマ演算子を使うと複数の式を評価できます。それらの式はカンマ ',' で区切られています。それぞれが評価されますが、最後の結果のみが返却されます。

例:

let a = (1 + 2, 3 + 4);

alert( a ); // 7 (3 + 4 の結果)

ここで、最初の式 1 + 2 は評価され、その結果はどこかへ捨てられます。次に 3 + 4 が評価され、結果として返却されます。

カンマはとても優先順が低いです

カンマ演算子はとても優先順位が低いことに注意してください。 = よりも低いため、上の例では丸括弧が重要です。

それらがない場合: a = 1 + 2, 3 + 4+ を最初に評価し、数値を a = 3, 7 に加算します。次に代入演算子 =a = 3 を割り当てます。そして、カンマのあとの 7 は処理されず、無視されます。

なぜこのような、最後の部分を除いてすべてを捨てる演算子が必要なのでしょうか?

より複雑な構造において、1行で複数のアクションを書くときに使用される場合があります。

例:

// 1行に3つの演算子
for (a = 1, b = 3, c = a * b; a < 10; a++) {
 ...
}

このようなトリックは多くのJavaScriptフレームワークで利用されているため、ここで言及しています。しかし通常それらはコードの可読性を下げます。なので、そのように書く前によく考えるべきです。

タスク

重要性: 5

下のコードが実行されたあと、変数 a, b, c, d はいくつになるでしょう?

let a = 1, b = 1;

let c = ++a; // ?
let d = b++; // ?

答えは次の通りです:

  • a = 2
  • b = 2
  • c = 2
  • d = 1
let a = 1, b = 1;

alert( ++a ); // 2, 前置式は新しい値を返します
alert( b++ ); // 1, 後置式は古い値を返します

alert( a ); // 2, 1回インクリメントされています
alert( b ); // 2, 1回インクリメントされています
重要性: 3

下のコードが実行されたあと、ax はいくつになるでしょう?

let a = 2;

let x = 1 + (a *= 2);

答えは次の通りです:

  • a = 4 (2で掛けられています)
  • x = 5 (1 + 4 と計算されます)
チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。