# 演算子 ### 演算子とは 演算処理を記号などで表現したものです。(足し算をする`+`など) ### 被演算子(オペランド)とは 演算する対象のことです。 ```javascript= 1 + 2; //加算演算を行う際に+演算子の対象となっている1と2という2つの値がオペランドです。 ``` +演算子に対して前後に2つのオペランドを取る演算子を**二項演算子**と呼びます。 ``` // 二項演算子とオペランドの関係 左オペランド 演算子 右オペランド ``` また、1つの演算子に対して1つのオペランドを取る演算子を**単項演算子**と呼び、数値をインクリメントする`++`演算子は、前後どちらか一方にオペランドを置きます。 ```javascript= let num = 1; num++; // または ++num; ``` 単項演算子と二項演算子で同じ記号を使うことがあるため、呼び方を変えています。 --- ## 本章でのポイント 1. 各演算子ごとにそれぞれの処理について学んでいきます。 2. 演算子の中でも比較演算子は、JavaScriptでも特に挙動が理解しにくい暗黙的な型変換という問題と密接な関係があります。 3. 演算子をひととおり見た後に、暗黙的な型変換と明示的な型変換について学んでいきます。 --- ### 二項演算子 #### プラス演算子(`+`) * 2つの数値を加算する演算子です。 ```javascript= console.log(1 + 1); // => 2 ``` > JavaScriptでは、数値は内部的にIEEE 754方式の浮動小数点数として表現されています ( [データ型とリテラルを参照](https://hackmd.io/kSZaLK10Tui9c2yRq987pg?both))。 そのため、整数と浮動小数点数の加算もプラス演算子で行えます。 ```javascript= console.log(10 + 0.5); // => 10.5 ``` #### 文字列結合演算子(`+`) * 数値の加算に利用したプラス演算子(`+`)は、文字列の結合に利用できます。 * 文字列結合演算子(`+`)は、2つの文字列を結合した文字列を返します。 ```javascript= const value = "文字列" + "結合"; console.log(value); // => "文字列結合" //プラス演算子(+)は数値同士と文字列同士の演算をします。 ``` #### マイナス演算子(`-`) * 2つの数値を減算する演算子です。左オペランドから右オペランドを減算した値を返します。 ```javascript= console.log(1 - 1); // => 0 console.log(10 - 0.5); // => 9.5 ``` #### 乗算演算子(`*`) * 2つの数値を乗算する演算子です。 ```javascript= console.log(2 * 8); // => 16 console.log(10 * 0.5); // => 5 ``` #### 除算演算子(`/`) * 2つの数値を除算する演算子です。左オペランドを右オペランドで除算した値を返します。 ```javascript= console.log(8 / 2); // => 4 console.log(10 / 0.5); // => 20 ``` #### 剰余演算子(`%`) * 2つの数値のあまりを求める演算子です。左オペランドを右オペランドで除算したあまりを返します。 ```javascript= console.log(8 % 2); // => 0 console.log(9 % 2); // => 1 console.log(10 % 0.5); // => 0 console.log(10 % 4.5); // => 1 ``` #### [ES2016] べき乗演算子(`**`) * 2つの数値のべき乗を求める演算子です。 左オペランドを右オペランドでべき乗した値を返します。 ```javascript= // べき乗演算子(ES2016)で2の4乗を計算 console.log(2 ** 4); // => 16 ``` * べき乗演算子と同じ動作をするMath.powメソッドがあります。 ```javascript= console.log(Math.pow(2, 4)); // => 16 ``` > べき乗演算子はES2016で後から追加された演算子であるため、関数と演算子がそれぞれ存在しています。 他の二項演算子は演算子が先に存在していたため、Mathには対応するメソッドがありません。 --- ### 単項演算子(算術) * 単項演算子は、1つのオペランドを受け取り処理する演算子です。 #### 単項プラス演算子(`+`) * 単項演算子の`+`はオペランドを数値に変換します。 ```javascript= console.log(+1); // => 1 ``` > 数値の1を数値へ変換するため、結果は変わらず数値の1です。 +数値のように数値に対して、単項プラス演算子をつけるケースはほぼ無いでしょう。 ```javascript= console.log(+"1"); // => 1 ``` > また、単項プラス演算子は、数値以外も数値へと変換し、数字(文字列)を数値へ変換しています。 * 一方、数値に変換できない文字列などはNaNという特殊な値へと変換されます。 ```javascript= // 数値ではない文字列はNaNという値に変換される console.log(+"文字列"); // => NaN ``` * NaNは"Not-a-Number"の略称で、数値ではないがNumber型の値を表現しています。 NaNはどの値とも(NaN自身に対しても)一致しない特性があり、Number.isNaNメソッドを使うことでNaNの判定を行えます。 ```javascript= // 自分自身とも一致しない console.log(NaN === NaN); // => false // Number型である console.log(typeof NaN); // => "number" // Number.isNaNでNaNかどうかを判定 console.log(Number.isNaN(NaN)); // => true ``` > しかし、単項プラス演算子は文字列から数値への変換に使うべきではありません。 なぜなら、Numberコンストラクタ関数やparseInt関数などの明示的な変換方法が存在するためです。 詳しくは[「暗黙的な型変換」](https://hackmd.io/Xb-8O2KkTXa5m5bR3PUVcw)の章で解説します。 #### 単項マイナス演算子(`-`) * 単項マイナス演算子はマイナスの数値を記述する場合に利用します。 ```javascript= console.log(-1); // => -1 ``` > マイナスの1という数値を -1 と書くことができるのは、単項マイナス演算子を利用しているからです。 * 単項マイナス演算子はマイナスの数値を反転できます。 そのため、"マイナスのマイナスの数値"はプラスの数値となります。 ```javascript= console.log(-(-1)); // => 1 ``` * 単項マイナス演算子も文字列などを数値へ変換します。 ```javascript= console.log(-"1"); // => -1 ``` * 数値へ変換できない文字列などをオペランドに指定した場合は、NaNという特殊な値になります。 そのため、単項プラス演算子と同じく、文字列から数値への変換に単項マイナス演算子を使うべきではありません。 ```javascript= console.log(-"文字列"); // => NaN ``` --- ### インクリメント演算子(`++`) * インクリメント演算子(++)は、オペランドの数値を+1する演算子です。 オペランドの前後どちらかにインクリメント演算子を置くことで、オペランドに対して値を+1した値を返します。 ```javascript= let num = 1; num++; console.log(num); // => 2 // 次のようにした場合と結果は同じ // num = num + 1; ``` > インクリメント演算子(`++`)は、オペランドの後ろに置くか前に置くかで、それぞれで評価の順番が異なります。 * 後置インクリメント演算子(`num++`)は、次のような順で処理が行われます。 1. `num`の評価結果を返す 2. `num`に対して`+1`する 3. `num++`が返す値は`+1`する前の値となります。 ```javascript= let x = 1; console.log(x++); // => 1 console.log(x); // => 2 ``` * 一方、前置インクリメント演算子(`++num`)は、次のような順で処理が行われます。 1. `num`に対して`+1`する 2. `num`の評価結果を返す 3. `++num`が返す値は`+1`した後の値となります。 ```javascript= let x = 1; console.log(++x); // => 2 console.log(x); // => 2 ``` > この2つの使い分けが必要となる場面は多くありません。 そのため、評価の順番が異なることだけを覚えておけば問題ないと言えます。 --- ### デクリメント演算子(`--`) * デクリメント演算子(`--`)は、オペランドの数値を`-1`する演算子です。 ```javascript= let num = 1; num--; console.log(num); // => 0 // 次のようにした場合と結果は同じ // num = num - 1; ``` * デクリメント演算子は、インクリメント演算子と同様に、オペランドの前後のどちらかに置くことができます。 デクリメント演算子も、前後どちらに置くかで評価の順番が変わります。 ```javascript= // 後置デクリメント演算子 let x = 1; console.log(x--); // => 1 console.log(x); // => 0 // 前置デクリメント演算子 let y = 1; console.log(--y); // => 0 console.log(y); // => 0 ``` --- ## 比較演算子 比較演算子はオペランド同士の値を比較し、真偽値を返す演算子です。 #### 厳密等価演算子(`===`) * 厳密等価演算子は、左右の2つのオペランドを比較します。 同じ型で同じ値である場合に、`true`を返します。 ```javascript= console.log(1 === 1); // => true console.log(1 === "1"); // => false ``` * オペランドがどちらもオブジェクトであるときは、 オブジェクトの参照が同じである場合に、`true`を返します。 ```javascript= // {} は新しいオブジェクトを作成している const objA = {}; const objB = {}; // 生成されたオブジェクトは異なる参照となる console.log(objA === objB); // => false // 同じ参照を比較している場合 console.log(objA === objA); // => true ``` > 空のオブジェクトリテラル(`{}`)同士を比較し、 オブジェクトリテラルは、新しいオブジェクトを作成します。 そのため、異なるオブジェクトを参照する変数を`===`で比較すると`false`を返します。 #### 厳密不等価演算子(`!==`) * 厳密不等価演算子は、左右の2つのオペランドを比較します。 異なる型または異なる値である場合に、`true`を返します。 ```javascript= console.log(1 !== 1); // => false console.log(1 !== "1"); // => true ``` > `===`を反転した結果を返す演算子となります。 #### 等価演算子(`==`) * 等価演算子(`==`)は、2つのオペランドを比較します。 同じデータ型のオペランドを比較する場合は、厳密等価演算子(`===`)と同じ結果になります。 ```javascript= console.log(1 == 1); // => true console.log("str" == "str"); // => true console.log("JavaScript" == "ECMAScript"); // => false // オブジェクトは参照が一致しているならtrueを返す // {} は新しいオブジェクトを作成している const objA = {}; const objB = {}; console.log(objA == objB); // => false console.log(objA == objA); // => true ``` しかし、等価演算子(`==`)はオペランド同士が異なる型の値であった場合に、 同じ型となるように**暗黙的な型変換**をしてから比較します。 そのため、次のような、見た目からは結果を予測できない挙動が多く存在します。 ```javascript= // 文字列を数値に変換してから比較 console.log(1 == "1"); // => true // "01"を数値にすると`1`となる console.log(1 == "01"); // => true // 真偽値を数値に変換してから比較 console.log(0 == false); // => true // nullの比較はfalseを返す console.log(0 == null); // => false // nullとundefinedの比較は常にtrueを返す console.log(null == undefined); // => true ``` 意図しない挙動となることがあるため、暗黙的な型変換が行われる等価演算子(`==`)を使うべきではありません。 代わりに、厳密等価演算子(`===`)を使い、異なる型を比較したい場合は明示的に型を合わせるべきです。 例外的に、等価演算子(`==`)が使われるケースとして、nullとundefinedの比較があります。 次のように、比較したいオペランドが `null` または `undefined` であることを判定したい場合に、 厳密等価演算子(`===`)では二度比較する必要があります。 等価演算子(`==`)では`null`と`undefined`の比較結果は`true`となるため、一度の比較でよくなります。 ```javascript= const value = undefined; /* または null */ // === では2つの値と比較しないといけない if (value === null || value === undefined) { console.log("valueがnullまたはundefinedである場合の処理"); } // == では null と比較するだけでよい if (value == null) { console.log("valueがnullまたはundefinedである場合の処理"); } ``` このように等価演算子(`==`)を使う例外的なケースはありますが、 等価演算子(`==`)は暗黙的な型変換をするため、バグを引き起こしやすいです。 そのため、仕組みを理解するまでは常に厳密等価演算子(`===`)を利用することを推奨します。 #### 不等価演算子(`!=`) * 不等価演算子(!=)は、2つのオペランドを比較し、等しくないなら`true`を返します。 ```javascript= console.log(1 != 1); // => false console.log("str" != "str"); // => false console.log("JavaScript" != "ECMAScript"); // => true console.log(true != true);// => false // オブジェクトは参照が一致していないならtrueを返す const objA = {}; const objB = {}; console.log(objA != objB); // => true console.log(objA != objA); // => false ``` 不等価演算子も、等価演算子(`==`)と同様に異なる型のオペランドを比較する際に、暗黙的な型変換をしてから比較します。 ```javascript= console.log(1 != "1"); // => false console.log(0 != false); // => false console.log(0 != null); // => true console.log(null != undefined); // => false ``` そのため、不等価演算子(`!=`)は、利用するべきではありません。 代わりに暗黙的な型変換をしない厳密不等価演算子(`!==`)を利用します。 #### 大なり演算子/より大きい(`>`) * 大なり演算子は、左オペランドが右オペランドより大きいならば、trueを返します。 ```javascript= console.log(42 > 21); // => true console.log(42 > 42); // => false ``` #### 大なりイコール演算子/以上(`>=`) * 大なりイコール演算子は、左オペランドが右オペランドより大きいまたは等しいならば、`true`を返します。 ```javascript= console.log(42 >= 21); // => true console.log(42 >= 42); // => true console.log(42 >= 43); // => false ``` #### 小なり演算子/より小さい(`<`) * 小なり演算子は、左オペランドが右オペランドより小さいならば、`true`を返します。 ```javascript= console.log(21 < 42); // => true console.log(42 < 42); // => false ``` #### 小なりイコール演算子/以下(`<=`) * 小なりイコール演算子は、左オペランドが右オペランドより小さいまたは等しいならば、`true`を返します。 ```javascript= console.log(21 <= 42); // => true console.log(42 <= 42); // => true console.log(43 <= 42); // => false ``` --- ### ビット演算子 * ビット演算子では、オペランドである数値を符号付き32ビット整数(`0`と`1`からなる32個のビットの集合)として扱います。 * 数値の対応表 | 2進数 | 10進数 | 16進数 | | -----:| ------:| ------:| | 0 | 0 | 0 | | 1 | 1 | 1 | | 10| 2 | 2 | | 11 | 3 | 3 | | 100 | 4 | 4 | | 101 | 5 | 5 | | 110 | 6 | 6 | | 1000 | 7 | 7 | | 1001 | 8 | 8 | | 1010 | 9 | 9 | | 1011 | 10 | A | | 1100 | 11 | B | | 1101 | 12 | C | | 1110 | 13 | D | | 1111 | 14 | E | | 10000 | 15 | F | たとえば、`1`という数値は符号付き32ビット整数のビットでは、`00000000000000000000000000000001` として表現されます。 わかりやすく4ビットごとに区切ると `0000_0000_0000_0000_0000_0000_0000_0001 `のような32ビットの集合となります。 符号付き32ビット整数では、先頭の最上位ビット(一番左のビット)は符号を表し、`0`の場合は正の値、`1`の場合は負の値であることを示しています。 ![1の符号付き32bit整数での表現](https://i.imgur.com/849PznQ.png) 符号付き32ビット整数では負の数値は、2の補数形式という形式で表現されます。 2の補数とは、それぞれのビットを反転して1ビットを足した値となります。 たとえば、`-1` という数値の符号付き32ビット整数は、次のように2の補数で求められます。 10進数の`1`は、符号付き32ビット整数では * `0000_0000_0000_0000_0000_0000_0000_0001`となる * `0000_0000_0000_0000_0000_0000_0000_0001`の各ビットを反転すると `1111_1111_1111_1111_1111_1111_1111_1110` となる * これに1ビットを足すと `1111_1111_1111_1111_1111_1111_1111_1111` となる これによって、-1の符号付き32ビット整数は `1111_1111_1111_1111_1111_1111_1111_1111` となります。 ![-1の符号付き32ビット整数での表現](https://i.imgur.com/3fXgWEf.png) 符号付き32ビット整数で表現できる数値の範囲は、`1000_0000_0000_0000_0000_0000_0000_0000`から `0111_1111_1111_1111_1111_1111_1111_1111`までとなります。 10進数に直すと`-(2^31)`(2の31乗の負の数)から `(2^31) - 1`(2の31乗から1引いた数)までとなります。 32ビットを超える数値については、32ビットをはみ出るビットが最上位(一番左)から順番に捨てられます。 これから見ていくビット演算子はオペランドを符号付き32ビット整数として扱い、その演算結果を10進数の数値として返します。 #### ビット論理積(`&`) * ビット論理積演算子(`&`)はビットごとのAND演算した結果を返します。 AND演算では、オペランドの各ビットがどちらも`1`の場合は`1`となり、それ以外の場合は`0`となります。 * AND演算の真理値表 | A | B | A AND B | |:---:|:---:|:-------:| | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 1 | 次のコードでは、10進数の`15`と`9`を**AND**演算しています。 `15`は、符号付き32ビット整数では`0000_0000_0000_0000_0000_0000_0000_1111`となります。 `9`は、符号付き32ビット整数では`0000_0000_0000_0000_0000_0000_0000_1001`となります。 これらをAND演算した結果は`0000_0000_0000_0000_0000_0000_0000_1001`となり、10進数の値である`9`を返します。 ```javascript= console.log(15 & 9); // => 9 // 同じ位の各ビット同士をAND演算する(上位の`0`は省略) // 1111 // 1001 // ---- // 1001 console.log(0b1111 & 0b1001); // => 0b1001 ``` #### ビット論理和(`|`) * ビット論理和演算子(`|`)はビットごとの**OR**演算した結果を返します。 OR演算では、オペランドの各ビットがどちらか片方でも`1`の場合は`1`となり、両方とも`0`の場合は`0`となります。 * OR演算の真理値表 | A | B | A OR B | |:---:|:---:|:------:| | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 1 | ```javascript= console.log(15 | 9); // => 15 // 同じ位の各ビット同士をOR演算する(上位の`0`は省略) // 1111 // 1001 // ---- // 1111 console.log(0b1111 | 0b1001); // => 0b1111 ``` #### ビット排他的論理和(`^`) * ビット排他的論理和演算子(`^`)はビットごとの**XOR**演算した結果を返します。 XOR演算では、オペランドのビットが異なるなら`1`、両方とも同じなら`0`となります。 * XOR演算の真理値表 | A | B | A XOR B | |:---:|:---:|:-------:| | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 | ```javascript= console.log(15 ^ 9); // => 6 // 同じ位の各ビット同士をXOR演算する(上位の`0`は省略) // 1111 // 1001 // ---- // 0110 console.log(0b1111 ^ 0b1001); // => 0b0110 ``` #### ビット否定(`~`) * 単項演算子の否定演算子(`~`)はオペランドの各ビットを反転した値を返します。 これは`1`の補数として知られている値と同じものです。 * NOT演算の真理値表 | A | NOT A | |:---:|:-----:| | 0 | 1 | | 1 | 0 | 次のコードでは、10進数で`15`を否定演算子(`~`)で各ビットを反転させた値を得ています。 `15` は `0000_0000_0000_0000_0000_0000_0000_1111`です。 各ビットを反転させると`1111_1111_1111_1111_1111_1111_1111_0000` となり、10進数では`-16` となります。 ```javascript= console.log(~15); // => -16 ``` `~x`のように`x`をビット否定演算子で演算した結果は、`-(x + 1)`となります。 この性質を利用する形で、ビット否定演算子(`~`)はビット演算以外でも使われていることがあります。 文字列(Stringオブジェクト)が持つ`indexOf`メソッドは、マッチする文字列を見つけて、そのインデックス(位置)を返すメソッドです。 この`indexOf`メソッドは、検索対象が見つからない場合には`-1`を返します。 ```javascript= const str = "森森本森森"; // 見つかった場合はインデックスを返す // JavaScriptのインデックスは0から開始するので2を返す console.log(str.indexOf("本")); // => 2 // 見つからない場合は-1を返す console.log(str.indexOf("火")); // => -1 ``` 否定演算子(`~`)は1の補数を返すため、`~(-1)`は`0`となります。 ```javascript= console.log(~0); // => -1 console.log(~(-1)); // => 0 ``` JavaScriptでは`0`も、if文では`false`として扱われます。 そのため、`~indexOf`の結果が`0`となるのは、その文字列が見つからなかった場合だけとなります。 次のコードのように否定演算子(`~`)と`indexOf`メソッドを使ったイディオムが一部では使われていました。 ```javascript= const str = "森森木森森"; // indexOfメソッドは見つからなかった場合は -1 を返す if (str.indexOf("木") !== -1) { console.log("木を見つけました"); } // 否定演算子(`~`)で同じ動作を実装 // (~(-1)) は 0 となるため、見つからなかった場合はif文の中身は実行されない if (~str.indexOf("木")) { console.log("木を見つけました"); } ``` ES2015では、文字列(Stringオブジェクト)に`includes`メソッドが実装されました。 `includes`メソッドは指定した文字列が含まれているかを真偽値で返します。 ```javascript= const str = "森森木森森"; if (str.includes("木")) { console.log("木を見つけました"); } ``` そのため、否定演算子(`~`)と`indexOf`メソッドを使ったイディオムは、`includes`メソッドに置き換えられます。 #### 左シフト演算子(`<<`) * 左シフト演算子は、数値である`num`を`bit`の数だけ左へシフトします。 左にあふれたビットは破棄され、`0`のビットを右から詰めます。 ```javascript= num << bit; ``` 次のコードでは、`9`を2ビット分だけ左へシフトしています。 ```javascript= console.log( 9 << 2); // => 36 console.log(0b1111 << 2); // => 0b11_1100 ``` #### 右シフト演算子(`>>`) * 右シフト演算子は、数値であるnumをbitの数だけ右へシフトします。 右にあふれたビットは破棄され、左端のビットのコピーを左から詰めます。 ```javascript= num >> bit; ``` 次のコードでは、`-9`を2ビット分だけ右へシフトしています。 左端のビットのコピーを使うため、常に符号は維持されます。 ```javascript= console.log((-9) >> 2); // => -3 // 1111_1111_1111_1111_1111_1111_1111_0111 >> 2 // => 1111_1111_1111_1111_1111_1111_1111_1101 ``` #### ゼロ埋め右シフト演算子(`>>>`) * ゼロ埋め右シフト演算子は、数値である`num`を`bit`の数だけ右へシフトするのは右シフト演算子(`>>`)と同じです。 異なる点としては右にあふれたビットは破棄され、`0`のビットを左から詰めます。 次のコードでは、`-9`を2ビット分だけゼロ埋め右シフトしています。 左端のビットは`0`となるため、常に正の値となります。 ```javascript= console.log((-9) >>> 2); // => 1073741821 // 1111_1111_1111_1111_1111_1111_1111_0111 >>> 2 // => 0011_1111_1111_1111_1111_1111_1111_1101 ``` --- ### 代入演算子(`=`) * 代入演算子(`=`)は変数に対して値を代入します。 代入演算子については[「変数と宣言」](https://)の章も参照してください。 ```javascript= let x = 1; x = 42; console.log(x); // => 42 ``` また、代入演算子は二項演算子と組み合わせて利用できます。 `+=`、`-=`、`*=`、`/=`、`%=`、`<<=`、`>>=`、`>>>=`、`&=`、`^=`、`|=`、`**=`のように、演算した結果を代入できます。 ```javascript= let num = 1; num += 10; // num = num + 10; と同じ console.log(num); // => 11 ``` #### [ES2015] 分割代入(Destructuring assignment) 今まで見てきた代入演算子は1つの変数に値を代入するものでした。 分割代入を使うことで、配列やオブジェクトの値を複数の変数へ同時に代入できます。 分割代入は短縮記法のひとつでES2015から導入された構文です。 分割代入は、代入演算子(`=`)を使うのは同じですが、左辺のオペランドが配列リテラルやオブジェクトリテラルとなります。 次のコードでは、右辺の配列の値を、左辺の配列リテラルの対応するインデックスに書かれた変数名へ代入します。 ```javascript= const array = [1, 2]; // aには`array`の0番目の値、bには1番目の値が代入される const [a, b] = array; console.log(a); // => 1 console.log(b); // => 2 ``` これは、次のように書いたのと同じ結果になります。 ```javascript= const array = [1, 2]; const a = array[0]; const b = array[1]; ``` 同様にオブジェクトも分割代入に対応しています。 オブジェクトの場合は、右辺のオブジェクトのプロパティ値を、左辺に対応するプロパティ名へ代入します。 ```javascript= const obj = { "key": "value" }; // プロパティ名`key`の値を、変数`key`として定義する const { key } = obj; console.log(key); // => "value" ``` これは、次のように書いたのと同じ結果になります。 ```javascript= const obj = { "key": "value" }; const key = obj.key; ``` --- ### 論理演算子 * 論理演算子は基本的に真偽値を扱う演算子で**AND**(かつ)、**OR**(または)、**NOT**(否定)を表現できます。 #### AND演算子(`&&`) * AND演算子(`&&`)は、左辺の値の評価結果が`true`ならば、右辺の評価結果を返します。 一方で、左辺の値の評価結果が`false`ならば、そのまま左辺の値を返します。 ```javascript= // 左辺はtrueであるため、右辺の評価結果を返す console.log(true && "右辺の値"); // => "右辺の値" // 左辺がfalseであるなら、その時点でfalseを返す // 右辺は評価されない console.log(false && "右辺の値"); // => false ``` AND演算子(`&&`)は、左辺の評価が`false`の場合、オペランドの右辺は評価されません。 次のように、左辺が`false`の場合は、右辺に書いた`console.log`関数自体が実行されません。 ```javascript= // 左辺がtrueなので、右辺は評価される true && console.log("このコンソールログは実行されます"); // 左辺がfalseなので、右辺は評価されない false && console.log("このコンソールログは実行されません"); ``` このような値が決まった時点でそれ以上評価しないことを**短絡評価**と呼びます。 また、AND演算子は左辺を評価する際に、左辺を真偽値へと[暗黙的な型変換](https://hackmd.io/Xb-8O2KkTXa5m5bR3PUVcw)をしてから判定します。 真偽値への暗黙的な型変換では、次に挙げる値は`false`へ変換されます。 * `false` * `undefined` * `null` * `0` * `0n` * `NaN` * `""`(空文字列) 暗黙的な型変換によって`false`に変換されるこれらの値をまとめて**falsy**な値と呼びます。 falsyではない値は、`true`へと変換されます。 `true`へと変換される値の種類は多いため、`false`へと変換されない値は`true`となることは覚えておくとよいです。 このオペランドを真偽値に変換してから評価するのは`AND`、`OR`、`NOT`演算子で共通の動作です。 次のように、AND演算子(`&&`)は左辺を真偽値へと変換した結果が`true`の場合に、右辺の評価結果を返します。 つまり、左辺がfalsyの場合は、右辺は評価されません。 ```javascript= // 左辺はfalsyではないため、評価結果として右辺を返す console.log("文字列" && "右辺の値"); // => "右辺の値" console.log(42 && "右辺の値"); // => "右辺の値" // 左辺がfalsyであるため、評価結果として左辺を返す console.log("" && "右辺の値"); // => "" console.log(0 && "右辺の値"); // => 0 console.log(null && "右辺の値"); // => null ``` AND演算子は、if文と組み合わせて利用することが多い演算子です。 次のように、`value`がString型で **かつ** 値が`"str"`である場合という条件をひとつの式として書くことができます。 ```javascript= const value = "str"; if (typeof value === "string" && value === "str") { console.log(`${value} is string value`); } // if文のネストで書いた場合と結果は同じとなる if (typeof value === "string") { if (value === "str") { console.log(`${value} is string value`); } } ``` このときに、`value`がString型でない場合は、その時点でif文の条件式は`false`となります。 そのため、`value`がString型ではない場合は、AND演算子(`&&`)の右辺は評価されずに、if文の中身も実行されません。 AND演算子(`&&`)を使うと、if文のネストに比べて短く書くことができます。 しかし、if文が3重4重にネストしているのは複雑なのと同様に、 AND演算子やOR演算子が3つ4つ連続すると複雑で読みにくいコードとなります。 その場合は抽象化ができないかを検討するべきサインとなります。 #### OR演算子(`||`) * OR演算子(`||`)は、左辺の値の評価結果が`true`ならば、そのまま左辺の値を返します。 一方で、左辺の値の評価結果が`false`であるならば、右辺の評価結果を返します。 ```javascript= // 左辺がtrueなので、左辺の値が返される console.log(true || "右辺の値"); // => true // 左辺がfalseなので、右辺の値が返される console.log(false || "右辺の値"); // => "右辺の値" ``` OR演算子(`||`)は、左辺の評価が`true`の場合、オペランドの右辺を評価しません。 これは、AND演算子(`&&`)と同様の短絡評価となるためです。 ```javascript= // 左辺がtrueなので、右辺は評価されない true || console.log("このコンソールログは実行されません"); // 左辺がfalseなので、右辺は評価される false || console.log("このコンソールログは実行されます"); ``` また、OR演算子は左辺を評価する際に、左辺を真偽値へと暗黙的な型変換します。 次のように、OR演算子は左辺がfalsyの場合には右辺の値を返します。 ```javascript= // 左辺がfalsyなので、右辺の値が返される console.log(0 || "左辺はfalsy"); // => "左辺はfalsy" console.log("" || "左辺はfalsy"); // => "左辺はfalsy" console.log(null || "左辺はfalsy"); // => "左辺はfalsy" // 左辺はfalsyではないため、左辺の値が返される console.log(42 || "右辺の値"); // => 42 console.log("文字列" || "右辺の値"); // => "文字列" ``` OR演算子は、if文と組み合わせて利用することが多い演算子です。 次のように、`value`が`0`**または**`1`の場合にif文の中身が実行されます。 ```javascript= const value = 1; if (value === 0 || value === 1) { console.log("valueは0または1です。"); } ``` #### NOT演算子(`!`) * NOT演算子(`!`)は、オペランドの評価結果が`true`ならば、`false`を返します。 一方で、オペランドの評価結果が`false`ならば、`true`を返します。 つまり、オペランドの評価結果を反転した真偽値を返します。 ```javascript= console.log(!false); // => true console.log(!true); // => false ``` NOT演算子(`!`)もAND演算子(`&&`)とOR演算子(`||`)と同様に真偽値へと[暗黙的な型変換](https://hackmd.io/Xb-8O2KkTXa5m5bR3PUVcw)します。 falsyである値は`true`へ変換され、falsyではない値は`false`へと変換されます。 ```javascript= // falsyな値は`true`となる console.log(!0); // => true console.log(!""); // => true console.log(!null); // => true // falsyではない値は`false`となる console.log(!42); // => false console.log(!"文字列"); // => false ``` NOT演算子は必ず真偽値を返すため、次のように2つNOT演算子を重ねて真偽値へ変換するという使い方も見かけます。 たとえば、`!!falsyな値`のように2度反転すれば`false`になります。 ```javascript= const str = ""; // 空文字列はfalsyであるため、true -> falseへと変換される console.log(!!str); // => false ``` このようなケースの多くは、比較演算子を使うなどより明示的な方法で、真偽値を得ることができます。 安易に`!!`による変換に頼るよりは別の方法を探してみるのがいいでしょう。 ```javascript= const str = ""; // 空文字列(長さが0より大きな文字列)でないことを判定 console.log(str.length > 0); // => false ``` #### [ES2020] Nullish coalescing演算子(`??`) * Nullish coalescing演算子(`??`)は、左辺の値が**nullish**であるならば、右辺の評価結果を返します。 **nullish**とは、評価結果が`null`または`undefined`となる値のことです。 ```javascript= // 左辺がnullishであるため、右辺の値の評価結果を返す console.log(null ?? "右辺の値"); // => "右辺の値" console.log(undefined ?? "右辺の値"); // => "右辺の値" // 左辺がnullishではないため、左辺の値の評価結果を返す console.log(true ?? "右辺の値"); // => true console.log(false ?? "右辺の値"); // => false console.log(0 ?? "右辺の値"); // => 0 console.log("文字列" ?? "右辺の値"); // => "文字列" ``` Nullish coalescing演算子(`??`)とOR演算子(`||`)は、値のデフォルト値を指定する場合によく利用されています。 OR演算子(`||`)は左辺がfalsyの場合に右辺を評価するため、意図しない結果となる場合が知られています。 次のコードは、`inputValue`が未定義だった場合に、`value`に対するデフォルト値をOR演算子(`||`)で指定しています。 `inputValue`が未定義(`undefined`)の場合は、意図したようにOR演算子(`||`)の右辺で指定した`42`が入ります。 しかし、`inputValue`が`0`という値であった場合は、`0`はfalsyであるため`value`には右辺の`42`が入ります。 これでは`0`という値が扱えないため、意図しない動作となっています。 ```javascript= const inputValue = 任意の値または未定義; // `inputValue`がfalsyの場合は、`value`には`42`が入る // `inputValue`が`0`の場合は、`value`に`42`が入ってしまう const value = inputValue || 42; console.log(value); ``` この問題を解決するためにES2020でNullish coalescing演算子(`??`)が導入されています。 Nullish coalescing演算子(`??`)では、左辺がnullishの場合のみ、`value`に右辺で指定した`42`が入ります。 そのため、`inputValue`が`0`という値が入った場合は、`value`にはそのまま`inputValue`の値である`0`が入ります。 ```javascript= const inputValue = 任意の値または未定義; // `inputValue`がnullishの場合は、`value`には42が入る // `inputValue`が`0`の場合は、`value`に`0`が入る const value = inputValue ?? 42; console.log(value); ``` --- ## 条件(三項)演算子(`?`と:) * 条件演算子(`?`と`:`)は三項をとる演算子であるため、三項演算子とも呼ばれます。 条件演算子は条件式を評価した結果が`true`ならば、`Trueのとき処理する式`の評価結果を返します。 条件式が`false`である場合は、`False`のとき処理する式の評価結果を返します。 ```javascript= 条件式 ? Trueのとき処理する式 : Falseのとき処理する式; ``` if文との違いは、条件演算子は式として書くことができるため値を返します。 次のように、`条件式`の評価結果により`"A"` または `"B"` どちらかを返します。 ```javascript= const valueA = true ? "A" : "B"; console.log(valueA); // => "A" const valueB = false ? "A" : "B"; console.log(valueB); // => "B" ``` 条件分岐による値を返せるため、条件によって変数の初期値が違う場合などに使われます。 次の例では、`text`文字列に`prefix`となる文字列を先頭につける関数を書いています。 `prefix`の第二引数を省略したり文字列ではないものが指定された場合に、デフォルトの`prefix`を使います。 第二引数が省略された場合には、`prefix`に`undefined`が入ります。 条件演算子の評価結果は値を返すので、`const`を使って宣言と同時に代入できます。 ```javascript= function addPrefix(text, prefix) { // `prefix`が指定されていない場合は"デフォルト:"を付ける const pre = typeof prefix === "string" ? prefix : "デフォルト:"; return pre + text; } console.log(addPrefix("文字列")); // => "デフォルト:文字列" console.log(addPrefix("文字列", "カスタム:")); // => "カスタム:文字列" ``` if文を使った場合は、宣言と代入を分ける必要があるため、`const`を使うことができません。 ```javascript= function addPrefix(text, prefix) { let pre = "デフォルト:"; if (typeof prefix === "string") { pre = prefix; } return pre + text; } console.log(addPrefix("文字列")); // => "デフォルト:文字列" console.log(addPrefix("文字列", "カスタム:")); // => "カスタム:文字列" ``` --- ## グループ化演算子(`(`と`)`) * グループ化演算子は複数の二項演算子が組み合わさった場合に、演算子の優先順位を明示できる演算子です。 たとえば、次のようにグループ化演算子で囲んだ部分が最初に処理されるため、結果も変化します。 ```javascript= const a = 1; const b = 2; const c = 3; console.log(a + b * c); // 7 console.log((a + b) * c); // => 9 ``` 演算子の優先順位はECMAScript仕様で定義されていますが、演算子の優先度をすべて覚えるのは難しいです。 演算子の優先順位の中でグループ化演算子は優先される演算子となり、グループ化演算子を使って優先順位を明示できます。 次のようなグループ化演算子を使わずに書いたコードを見てみましょう。 `x`が`true`または、`y`かつ`z`が`true`であるときに処理されます。 ```javascript= if (x || y && z) { // x が true または // y かつ z が true } ``` ひとつの式に複数の種類の演算子が出てくると読みにくくなる傾向があります。 このような場合にはグループ化演算子を使い、結合順を明示して書くようにしましょう。 ```javascript= if (x || (y && z)) { // x が true または // y かつ z が true } ``` しかし、ひとつの式で多数の演算をするよりも、式自体を分けたほうが読みやすい場合もあります。 次のように`a`と`b`が文字列型 または `x`と`y`が数値型の場合に処理するif文を考えてみます。 グループ化演算子を使い、そのまま1つの条件式で書くことも可能ですが、読みにくくなってしまいます。 ```javascript= if ((typeof a === "string" && typeof b === "string") || (typeof x === "number" && typeof y === "number")) { // `a`と`b`が文字列型 または // `x`と`y`が数値型 } ``` このように無理して1つの式(1行)で書くよりも、条件式を分解してそれぞれの結果を変数として定義したほうが読みやすくなる場合もあります。 ```javascript= const isAbString = typeof a === "string" && typeof b === "string"; const isXyNumber = typeof x === "number" && typeof y === "number"; if (isAbString || isXyNumber) { // `a`と`b`が文字列型 または // `x`と`y`が数値型 } ``` そのため、グループ化演算子ですべての条件をまとめるのではなく、 それぞれの条件を分解して名前をつける(変数として定義する)ことも重要です。 --- ## カンマ演算子(`,`) カンマ演算子(`,`)は、カンマ(`,`)で区切った式を左から順に評価し、 最後の式の評価結果を返します。 次の例では、`式1`、`式2`、`式3`の順に評価され、`式3`の評価結果を返します。 ````javascript=` 式1, 式2, 式3; ``` これまでに、カンマで区切るという表現は、`const`による変数宣言などでも出てきました。 左から順に実行する点ではカンマ演算子の挙動は同じものですが、構文としては似て非なるものです。 ```javascript= const a = 1, b = 2, c = a + b; console.log(c); // => 3 ``` 一般にカンマ演算子を利用する機会はほとんどないため、「カンマで区切った式は左から順に評価される」ということだけを知っていれば問題ありません。 --- ## まとめ この章では演算子について学びました。 * 演算子はよく利用する演算処理を記号などで表現したもの * 四則演算から論理演算などさまざまな種類の演算子がある * 演算子には優先順位が定義されており、グループ化演算子で明示できる --- ## みなさんも何かあれば!!! ### Rubyのインクリメント Rubyはインクリメント(`++`)やデクリメント(`--`)が使えない。 代わりに`i += 1`や`succ`、`next`を使う。 [Ruby にインクリメント演算子のようなものが無い理由](https://blog.tokoyax.com/entry/ruby/increment) ### Reactでよく使うもの * 厳格等価演算子(`===`) * AND演算子(`&&`) * OR演算子(`||`) * NOT演算子(`!`) * 分割代入 * 条件(三項)演算子 ```ruby if(currnt_user && (logged_in?)) ```