ほとんどのプログラミング言語には、AND
やOR
を表す論理演算子が用意されている。
そして、左辺を評価した時点で論理式の結果が確定した場合には右辺の評価を行わないことを、ショートサーキット評価(短絡評価)という。
例えば、A AND B
という論理式があった場合、A
がfalse
なら、その時点で式全体の結果はfalse
で確定するため、B
がどうであるかについてはチェックしない。
JavaScriptの論理和演算子||
と論理積演算子&&
も、ショートサーキット評価を行う。
これを利用することで、コードを簡略化したり、パフォーマンスを向上させたりすることが出来る。
論理演算子の挙動
ショートサーキット評価を利用するためにはまず、論理演算子がどのように動くのかを理解しないといけない。
なお、以下の説明は、どのような結果を生むのかについての説明であり、内部的な挙動については正確ではない可能性がある。
まずは論理和演算子||
から。
左のオペランドを真偽値に型変換して評価し、それがtrue
だった場合は左オペランドを、false
だった場合は右オペランドを、返す。
左オペランドを返す場合、右オペランドは評価自体を行わない。
let value; console.log(value || 'valueが未定義です。'); // valueが未定義です。 value = 7; console.log(value || 'valueが未定義です。'); // 7
論理積演算子&&
はその逆で、左オペランドがfalse
だった場合は左オペランドを返し、true
だった場合は右オペランドを返す。
左オペランドを返す場合、右オペランドは評価自体を行わない。
let value; console.log(value && 'valueが未定義です。'); // undefined value = 7; console.log(value && 'valueが未定義です。'); // valueが未定義です。
具体的な使い方
ショートサーキット評価を利用することで、条件分岐を簡単に書くことが出来る。
以下は、&&
を使った書き方。
function checkValue(arg){ if(typeof arg === 'number'){ console.log(`${arg}は数値型です。`); }; // 1は数値型です。 typeof arg === 'number' && console.log(`${arg}は数値型です。`); // 1は数値型です。 }; checkValue(1);
checkValue()
で定義している2つの文は、どちらも同じ意味である。
論理式 && 文
は、if(論理式){文};
と同じ動きをする。
だがこのような書き方は、あまり見かけない。
よく使われるのは、以下の書き方である。
const useValue = inputtedValue || 10;
useValue
を定義しているが、inputtedValue
が存在すればその値を、存在しなければ10
を、代入している。
複数の論理和演算子
同じ演算子が並んでいる場合、左から順番に評価していく。
そのため、論理和演算子が複数並んでいたときは、左から順番に評価していき、true
に型変換できるものが出た時点で、それを返す。
true
に型変換できるものがなかった場合は、一番右のオペランドを返す。
let hoge, fuga, muu; console.log(hoge || fuga || muu); // undefined fuga = 5; console.log(hoge || fuga || muu); // 5 hoge = 7; console.log(hoge || foo || muu); // 7
ちなみに、最後の論理式でfoo
という未定義の変数を使っており、本来ならエラーになる。
だがこの論理式では、hoge
を評価した時点で論理式の評価は止まるので、エラーは出ない。このことからも、論理和演算子ではtrue
に型変換できるものが出た時点で評価が終わるということを、確認できる。
簡略化を行うべきか
このように、ショートサーキット評価を利用することで、コードを簡略化できる。
だがこれに対しては、批判的な意見も存在する。可読性が落ちるというのが、その理由である。
確かにやり過ぎれば、可読性は落ちるだろう。
だが例えば、先程の複数の論理和演算子などは、むしろ可読性が向上すると思う。
useData = person.address || person.city || person.country;
if(person.address) { useData = person.address; } else if(person.city) { useData = person.city; } else { useData = person.country; };
どちらも同じように動作するが、前者の書き方を知っていると、後者は冗長に感じる。
ショートサーキット評価とパフォーマンス
ショートサーキット評価を上手く使えば、可読性だけでなく、パフォーマンスも向上する。
let value; function checkFunc(arg){ console.log('checkFuncを呼び出しました。'); return arg; }; // コードA // 関数を呼び出さない。 value && checkFunc(true) ? console.log('ok') : console.log('ng') ; // コードB // 関数を呼び出してしまう。 checkFunc(true) && value ? console.log('ok') : console.log('ng') ;
上記のコードでは、value
とcheckFunc(true)
が共にtrue
として評価できる場合にok
を表示する。そうでない場合はng
を表示する。
この例ではvalue
はundefined
なので、ng
が表示される。
それはコードAでもコードBでも変わらない。
&&
の左右を入れ替えただけなのだから、最終的な結果はAもBも同じである。
だがパフォーマンスは異なる。
Aの場合、左オペランドがfalse
なので、その時点で、つまりcheckFunc(true)
を呼び出すことなく、論理式は終了する。
Bでは、左オペランドがcheckFunc(true)
なので、必ずこの関数が呼び出されてしまう。
このように、重い処理を右オペランドに配置することで、無用な呼び出しを避け、必要なときにのみ呼び出すように出来る。
上記の例ではcheckFunc()
はログを表示した後に引数を返すだけだが、ここでそれなりに重い処理を行っていた場合、パフォーマンスには差が生じる。
以下の例で、それを確認できる。
let value; function checkFunc(arg){ for(let i=0; i < 100; i++){ 1 + 1; }; return arg; }; const start = new Date(); for(let i=0; i < 1000000; i++){ value && checkFunc(true) ? 1 : 0 ; // checkFunc(true)は呼ばれない }; console.log(new Date() - start); // 18 〜 21
let value; function checkFunc(arg){ for(let i=0; i < 100; i++){ 1 + 1; }; return arg; }; const start = new Date(); for(let i=0; i < 1000000; i++){ checkFunc(true) && value ? 1 : 0 ; // checkFunc(true)を必ず呼び出す }; console.log(new Date() - start); // 240 〜 260
実際の秒数はもちろん環境によって異なるが、私の環境では、前者は18〜21ミリ秒、後者は240〜260ミリ秒、といったところだった。
つまり、10倍以上の差が出ていることが分かる。