2022年11月14日

基本的な演算子、算術演算

学校で多くの演算子を習いました。それらは、加算 +、乗算 *、減算 - などです。

この章では、簡単な演算子から始め、学校の算数では扱われない JavaScript 固有の側面に焦点を当てます。

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

先に進む前に、いくつかの一般的な用語を理解しましょう。

  • オペランドとは、演算子が適用される対象のことです。例えば、5 * 2 の乗算では、2 つのオペランドがあります。左オペランドは 5 で、右オペランドは 2 です。場合によっては、”オペランド”の代わりに “引数” と呼ばれることもあります。

  • 演算子は、オペランドが 1 つの場合は 単項です。たとえば、単項否定 - は数値の符号を反転させます。

    let x = 1;
    
    x = -x;
    alert( x ); // -1, unary negation was applied
  • 演算子は、オペランドが 2 つの場合は 二項です。同じマイナス記号が二項形式でも存在します。

    let x = 1, y = 3;
    alert( y - x ); // 2, binary minus subtracts values

    形式的には、上記の例では、同じ記号を共有する 2 つの異なる演算子があります。1 つは符号を反転させる単項演算子である否定演算子、もう 1 つは数値を別の数値から減算する二項演算子である減算演算子です。

算術演算

次の算術演算がサポートされています。

  • 加算 +
  • 減算 -
  • 乗算 *
  • 除算 /
  • 剰余 %
  • べき乗 **

最初の 4 つは簡単ですが、%** についてはいくつか説明が必要です。

剰余 %

剰余演算子 % は、見た目とは異なり、パーセントとは関係ありません。

a % b の結果は、ab で整数除算した際の剰余です。

例えば

alert( 5 % 2 ); // 1, the remainder of 5 divided by 2
alert( 8 % 3 ); // 2, the remainder of 8 divided by 3
alert( 8 % 4 ); // 0, the remainder of 8 divided by 4

べき乗 **

べき乗演算子 a ** b は、ab 乗します。

学校の数学では、ab と書きます。

例えば

alert( 2 ** 2 ); // 2² = 4
alert( 2 ** 3 ); // 2³ = 8
alert( 2 ** 4 ); // 2⁴ = 16

数学と同様に、べき乗演算子は非整数にも定義されています。

たとえば、平方根は 1/2 のべき乗です。

alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root)
alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root)

二項 + による文字列連結

学校の算術の範囲を超える JavaScript 演算子の特徴を見ていきましょう。

通常、プラス演算子 + は数値を加算します。

しかし、二項 + が文字列に適用されると、それらを結合 (連結) します。

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

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

例えば

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

最初のオペランドが文字列であるか、2 番目のオペランドが文字列であるかは問題ではありません。

より複雑な例を次に示します。

alert(2 + 2 + '1' ); // "41" and not "221"

ここでは、演算子が次々に動作します。最初の + は 2 つの数値を加算するので 4 を返し、次の + は文字列 1 を加算するので 4 + '1' = '41' のようになります。

alert('1' + 2 + 2); // "122" and not "14"

ここでは、最初のオペランドが文字列であるため、コンパイラは他の 2 つのオペランドも文字列として扱います。2'1' に連結されるため、'1' + 2 = "12" および "12" + 2 = "122" のようになります。

二項 + は、このような方法で文字列をサポートする唯一の演算子です。他の算術演算子は数値のみを処理し、常にオペランドを数値に変換します。

減算と除算のデモを次に示します。

alert( 6 - '2' ); // 4, converts '2' to a number
alert( '6' / '2' ); // 3, converts both operands to numbers

数値変換、単項 +

プラス + には、上記で使用した二項形式と単項形式の 2 つの形式があります。

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

例えば

// No effect on numbers
let x = 1;
alert( +x ); // 1

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

// Converts non-numbers
alert( +true ); // 1
alert( +"" );   // 0

実際には Number(...) と同じことを行いますが、より短く記述できます。

文字列を数値に変換する必要性は非常によく発生します。たとえば、HTML フォーム フィールドから値を取得する場合、それらは通常文字列です。それらを合計したい場合はどうすればよいでしょうか。

二項プラスはそれらを文字列として加算します。

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

alert( apples + oranges ); // "23", the binary plus concatenates strings

数値として扱う場合は、変換してから合計する必要があります。

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

// both values converted to numbers before the binary plus
alert( +apples + +oranges ); // 5

// the longer variant
// alert( Number(apples) + Number(oranges) ); // 5

数学者の観点からは、プラス記号が多いのは奇妙に見えるかもしれません。しかし、プログラマーの観点からは、特別なことは何もありません。単項プラスが最初に適用され、文字列を数値に変換し、次に二項プラスがそれらを合計します。

なぜ単項プラスが二項プラスよりも前に値に適用されるのでしょうか。これから説明するように、それは 優先度 が高いためです。

演算子の優先順位

式に複数の演算子がある場合、実行順序は、それらの 優先順位、つまり演算子のデフォルトの優先順位によって定義されます。

学校で、式 1 + 2 * 2 の乗算は加算より前に計算する必要があることを習いました。それがまさに優先順位の仕組みです。乗算は、加算よりも 優先順位が高い と言われます。

括弧は優先順位をオーバーライドするため、デフォルトの順序に満足できない場合は、括弧を使用して変更できます。たとえば、(1 + 2) * 2 と記述します。

JavaScript には多くの演算子があります。各演算子には対応する優先順位番号があります。番号が大きい方が最初に実行されます。優先順位が同じ場合は、実行順序は左から右になります。

次に、優先順位表からの抜粋を示します (これを覚えておく必要はありませんが、単項演算子が対応する二項演算子よりも優先順位が高いことに注意してください)。

優先順位 名前 記号
14 単項プラス +
14 単項否定 -
13 べき乗 **
12 乗算 *
12 除算 /
11 加算 +
11 減算 -
2 代入 =

ご覧のとおり、”単項プラス” の優先順位は 14 であり、”加算” (二項プラス) の 11 よりも高くなっています。そのため、式 "+apples + +oranges" では、単項プラスが加算よりも先に機能します。

代入

代入 = も演算子であることに注意しましょう。これは、優先順位表では非常に低い優先順位 2 でリストされています。

そのため、x = 2 * 2 + 1 のように変数を代入する場合、計算が最初に行われ、次に = が評価され、結果が x に格納されます。

let x = 2 * 2 + 1;

alert( x ); // 5

代入 = は値を返す

= が “魔法” の言語構成ではなく演算子であるという事実は、興味深い意味を持ちます。

JavaScript のすべての演算子は値を返します。これは + および - では明らかですが、= でも同様です。

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) です。これは、その後の評価に使用されます。

面白いコードですね。JavaScript ライブラリで時々見られるため、仕組みを理解しておく必要があります。

ただし、このようなコードは書かないでください。このようなトリックは、コードを明確にしたり、読みやすくしたりすることは決してありません。

代入の連鎖

もう 1 つの興味深い機能は、代入を連鎖できることです。

let a, b, c;

a = b = c = 2 + 2;

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

連鎖された代入は、右から左に評価されます。まず、一番右の式 2 + 2 が評価され、次に左側の変数 cb、および a に代入されます。最後に、すべての変数は単一の値を共有します。

繰り返しますが、読みやすさのために、このようなコードは数行に分割することをお勧めします。

c = 2 + 2;
b = c;
a = c;

特にコードをすばやく目でスキャンする場合、これは読みやすいです。

インプレース修正

多くの場合、変数に演算子を適用し、新しい結果を同じ変数に格納する必要があります。

例えば

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

この表記は、演算子 += および *= を使用して短縮できます。

let n = 2;
n += 5; // now n = 7 (same as n = n + 5)
n *= 2; // now n = 14 (same as n = n * 2)

alert( n ); // 14

すべての算術演算子およびビット単位演算子には、短い “修正と代入” 演算子が存在します: /=-= など。

このような演算子の優先順位は通常の代入と同じであるため、他のほとんどの計算の後で実行されます。

let n = 2;

n *= 3 + 5; // right part evaluated first, same as n *= 8

alert( n ); // 16

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

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

そのため、専用の演算子があります。

  • インクリメント ++ は変数を 1 増やします。

    let counter = 2;
    counter++;        // works the same as counter = counter + 1, but is shorter
    alert( counter ); // 3
  • デクリメント -- は変数を 1 減らします。

    let counter = 2;
    counter--;        // works the same as counter = counter - 1, but is shorter
    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++; // (*) changed ++counter to counter++

alert(a); // 1

(*) 行では、後置 形式 counter++counter をインクリメントしますが、古い 値 (インクリメント前) を返します。したがって、alert1 を表示します。

まとめると

  • インクリメント/デクリメントの結果が使用されない場合は、どの形式を使用しても違いはありません。

    let counter = 0;
    counter++;
    ++counter;
    alert( counter ); // 2, the lines above did the same
  • 値を増やし、すぐに 演算子の結果を使用する場合は、前置形式が必要です。

    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, because counter++ returns the "old" value

技術的には問題ありませんが、このような表記は通常、コードの可読性を低下させます。1行で複数の処理を行うため、良くありません。

コードを読んでいる際、「垂直方向」に素早く目を走らせると、counter++のような記述を見落としやすく、変数がインクリメントされたことが明らかにならない可能性があります。

「1行に1つのアクション」というスタイルをお勧めします。

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

ビット演算子

ビット演算子は、引数を32ビットの整数として扱い、それらの2進数表現レベルで動作します。

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

演算子の一覧

  • AND ( & )
  • OR ( | )
  • XOR ( ^ )
  • NOT ( ~ )
  • 左シフト ( << )
  • 右シフト ( >> )
  • ゼロ埋め右シフト ( >>> )

これらの演算子は、非常に低い(ビット単位の)レベルで数値を操作する必要がある場合に非常にまれに使用されます。Web開発ではほとんど使用しないため、すぐにこれらの演算子が必要になることはありませんが、暗号化などの特定の分野では役立ちます。必要に応じて、MDNのビット演算子の章をご覧ください。

カンマ

カンマ演算子,は、最も珍しく、最も特殊な演算子の1つです。コードを短く記述するために使用されることがあるため、何が起こっているのかを理解するために知っておく必要があります。

カンマ演算子を使用すると、カンマ,で区切られた複数の式を評価できます。各式は評価されますが、最後の式の結果のみが返されます。

例えば

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

alert( a ); // 7 (the result of 3 + 4)

ここでは、最初の式1 + 2が評価され、その結果は破棄されます。次に、3 + 4が評価され、結果として返されます。

カンマは非常に低い優先順位を持っています。

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

括弧がない場合:a = 1 + 2, 3 + 4は最初に+を評価し、数値を合計してa = 3, 7となります。次に、代入演算子=a = 3を代入し、残りは無視されます。これは(a = 1 + 2), 3 + 4のようになります。

最後の式以外のすべてを破棄する演算子が必要なのはなぜでしょうか?

時々、複数のアクションを1行にまとめるために、より複雑な構造で使用されます。

例えば

// three operations in one line
for (a = 1, b = 3, c = a * b; a < 10; a++) {
 ...
}

このようなトリックは多くのJavaScriptフレームワークで使用されています。そのため、ここで言及しています。ただし、通常はコードの可読性を向上させないため、使用する前によく考える必要があります。

課題

重要度: 5

以下のコードを実行後、すべての変数abc、および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, prefix form returns the new value
alert( b++ ); // 1, postfix form returns the old value

alert( a ); // 2, incremented once
alert( b ); // 2, incremented once
重要度: 3

以下のコードを実行後、axの値は何ですか?

let a = 2;

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

答えは次のとおりです。

  • a = 4 (2倍された)
  • x = 5 (1 + 4として計算された)
重要度: 5

以下の式の結果は何ですか?

"" + 1 + 0
"" - 1 + 0
true + false
6 / "3"
"2" * "3"
4 + 5 + "px"
"$" + 4 + 5
"4" - 2
"4px" - 2
"  -9  " + 5
"  -9  " - 5
null + 1
undefined + 1
" \t \n" - 2

よく考えて書き出し、答えと比較してください。

"" + 1 + 0 = "10" // (1)
"" - 1 + 0 = -1 // (2)
true + false = 1
6 / "3" = 2
"2" * "3" = 6
4 + 5 + "px" = "9px"
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
"  -9  " + 5 = "  -9  5" // (3)
"  -9  " - 5 = -14 // (4)
null + 1 = 1 // (5)
undefined + 1 = NaN // (6)
" \t \n" - 2 = -2 // (7)
  1. 文字列との加算"" + 1は、1を文字列に変換します。"" + 1 = "1"、そして"1" + 0となり、同じ規則が適用されます。
  2. 減算-は(ほとんどの数学演算と同様に)数値のみを操作し、空の文字列""0に変換します。
  3. 文字列との加算は、数値5を文字列に追加します。
  4. 減算は常に数値に変換するため、" -9 "を数値-9にします(周囲のスペースは無視されます)。
  5. 数値に変換すると、null0になります。
  6. 数値に変換すると、undefinedNaNになります。
  7. 文字列が数値に変換されるとき、スペース文字は文字列の先頭と末尾から削除されます。ここでは、文字列全体がスペース文字で構成されています(例:\t\n、およびそれらの間の「通常の」スペース)。そのため、空の文字列と同様に、0になります。
重要度: 5

以下は、ユーザーに2つの数値を尋ね、それらの合計を表示するコードです。

これは正しく動作しません。以下の例では、出力は12です(デフォルトのプロンプト値の場合)。

なぜですか?それを修正してください。結果は3である必要があります。

let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);

alert(a + b); // 12

その理由は、promptがユーザー入力を文字列として返すためです。

したがって、変数の値はそれぞれ"1""2"です。

let a = "1"; // prompt("First number?", 1);
let b = "2"; // prompt("Second number?", 2);

alert(a + b); // 12

+の前に文字列を数値に変換する必要があります。たとえば、Number()を使用するか、それらの前に+を付けるなどです。

たとえば、promptの直前に

let a = +prompt("First number?", 1);
let b = +prompt("Second number?", 2);

alert(a + b); // 3

またはalertの中で

let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);

alert(+a + +b); // 3

最新のコードでは、単項と二項の+の両方を使用しています。おかしいですね?

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善するための提案がある場合は、コメントする代わりにGitHub issueまたはプルリクエストを送信してください。
  • 記事で理解できないことがある場合は、詳しく説明してください。
  • 数語のコードを挿入する場合は、<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックス(plnkrjsbincodepen...)を使用してください。