LoginSignup
3
2

More than 1 year has passed since last update.

演算子の短絡評価のことを何も知らなかったので反省してちょっと勉強した

Posted at

はじめに

演算子には日常的にお世話になっているというのに「短絡評価ってのがあるらしいよ」と聞いても全くピンと来ず、「演算子のこと何も知らないんじゃ…?!」と気づいてしまったのでちょっとだけ勉強してみました。
(何かおかしいところがあったらご指摘いただけるとうれしいです)

そもそも短絡評価ってなんぞ

短絡評価は、条件付き評価を表す用語です。例えば、a && (b + c) という式において、a が偽値である場合、従属式である (b + c) は括弧で囲まれていても評価されません。この論理的分離演算子 ("OR") は「短絡的」といえるでしょう。論理的分離演算子の他にも、ほかに短絡が発生する演算子には、論理的結合 ("AND") 演算子、Null 合体演算子、オプション連鎖演算子、条件演算子があります。

上記だけ見るとちょっとよくわからないのですが、各演算子の挙動を見る限り、左辺を評価した時点で論理式の結果が確定すると、右辺の評価は行わず処理を終了すること短絡評価と言うようです。
「右辺の評価をするまでもなく論理式の結果は決まっているため、意味がないのでやらなくてOK」ってことですね。

  • 論理積演算子
  • 論理和演算子
  • Null合体演算子
  • オプション連鎖演算子
  • 条件演算子

MDNで短絡が発生するとされている各演算子について、以下で見ていきたいと思います。

論理積演算子 (&&)

一般的には、この演算子は左から右に向けて評価した際に最初の偽値のオペランドに遭遇したときにはその値を、またはすべてが真値であった場合は最後のオペランドの値を返します。

論理積の式は短絡演算子です。 各オペランドが論理値に変換されるとき、ある変換結果が false であった場合、論理積演算子は停止してその偽値のオペランドの元の値を返します。残りのオペランドは評価されません。

つまり、論理積演算子はfalsyなオペランドに遭遇した時点で評価を終了するという短絡評価を行うようです。
いくつか例を見てみます。

console.log(false && true) //①falseを返す
console.log(undefined && null) //②undefinedを返す
console.log(1 && 0) //③0を返す
console.log('Lチキ' && 'ファミチキ') //④'ファミチキ'を返す

上記の例の場合、
falseがfalsyな値のため、左辺を評価した時点で右辺の評価をせず、左辺のfalseを返す
②①同様に右辺の評価は行わず、左辺undefinedを返す
1がtruthyな値のため、左辺を評価後に右辺へ移り、右辺の0がfalsyな値のため、以降の評価は行わず0を返す
④③同様に左辺評価後に右辺へ移り、'ファミチキ'がtruthyな値(=全てのオペランドがtruthy)のため、最後のオペランドである右辺の'ファミチキ'を返す

①と②はそもそも右辺の評価をしていないため(短絡評価)、右辺がfalsyでもtruthyでも関係ありません。
③と④は同様に右辺を返しますが、挙動としては同じではありません。

論理和演算子 (||)

expr1 || expr2
expr1 が true に変換できる場合は expr1 を返し、それ以外の場合は expr2 を返します。

論理和の式は左から右へと評価され、下記の規則を使用して「短絡」評価が可能なように評価されます。
(真値の式) || expr は短絡評価で真値の式に評価されます。
短絡とは、上記の expr の部分が評価されず、したがって、これを行うことの副作用が効果を及ぼさないことを意味します(例えば、 expr が関数呼び出しであった場合、この場では呼び出されません)。これは、最初のオペランドが評価された時点で、すでに演算子の値が決定しているためです。

論理和演算子の場合は左から右へ順にオペランドを評価していき、truthyな値があればその時点で評価を終えるという短絡評価をするようです。

console.log(false || true) //①trueを返す
console.log(undefined || null) //②nullを返す
console.log(1 || 0) //③1を返す
console.log('Lチキ' || 'ファミチキ') //④'Lチキ'を返す

上記の例だと、
①②左辺がfalsyな値のため、右辺を返す(右辺のオペランドの真偽は関係なし)
③④左辺がtruthyな値のため、そこで評価を終了して(短絡評価)、左辺を返す

console.log(undefined || null || true || 0) //⑤trueを返す

オペランドの数を増やすと上記のようになります。
論理和演算子 (||)は最初に遭遇したtruthyな値を返し、全てfalsyな値だった場合は最後の値を返すと考えると良さそうです。

Null合体演算子 (??)

Null 合体演算子 (??) は論理演算子の一種です。この演算子は左辺が null または undefined の場合に右の値を返し、それ以外の場合に左の値を返します。

OR 演算子や AND 演算子と同様に、左辺が null でも undefined でもないことが判明した場合、右辺の式は評価されません。

Null合体演算子はnullかundefinedのオペランドに遭遇しなかった場合に処理を終了します。

console.log(null ?? true); //①true
console.log(undefined ?? null); //②null
console.log(null ?? undefined ?? true); //③true
console.log(0 ?? 1); //④0を返す

上記の例だと、
①〜③左辺がnull、もしくはundefined(=nullishな値)のため右辺を返す
④左辺がnullishな値でないので、その時点で評価を終了し(短絡評価)、0を返す
といった挙動になります。

オプショナルチェーン (?.)(オプション連鎖演算子)

?. 演算子の機能は . チェーン演算子と似ていますが、参照が nullish (null または undefined) の場合にエラーとなるのではなく、式が短絡され undefined が返されるところが異なります。関数呼び出しで使用すると、与えられた関数が存在しない場合、 undefined を返します。

式と一緒にオプショナルチェーン演算子を用いたとき、左側のオペランドが null または undefined である場合、その式は評価されなくなります。

オプショナルチェーンは参照がnullishだった場合に以降の処理を行わず、undefinedを返します。

const obj = {
    hoge: 'hoge'
};

console.log(obj?.hoge); //①'hoge'
console.log(obj?.fuga); //②undefined
console.log(obj.piyoFunc?.()); //②undefined

hogeプロパティが存在するので、'hoge'を返す
fugaプロパティが存在しないため、以降の処理が短絡されてundefinedが返ってくる
③はpiyoFunc()が存在しないため、以降の処理が短絡されてundefinedが返ってくる

条件 (三項) 演算子

条件 (三項) 演算子は JavaScript では唯一の、3 つのオペランドをとる演算子です。条件に続いて疑問符 (?)、そして条件が真値であった場合に実行する式、コロン (:) が続き、条件が偽値であった場合に実行する式が最後に来ます。

上記MDNのどこにも短絡評価の話が書かれてなーーーい!のですが、
条件式が真であれば左辺の式が、偽であれば右辺の式が評価される=評価されない方の式は短絡されるということなので、これも短絡評価しているってことになるんだと思います。

const hoge = true;

//左辺の処理のみが実行されて、処理が終了する
hoge ? console.log('true') : console.log('false'); //ture

まとめ

短絡評価が起きる条件をざっとまとめてみました。

  • 論理積演算子:falsyなオペランドに遭遇したら処理を終了する
  • 論理和演算子:truthyなオペランドに遭遇したら処理を終了する
  • Null合体演算子:nullでもundefinedでもないオペランドに遭遇したら処理を終了する
  • オプション連鎖演算子:参照がnullまたはundefinedだった場合、処理を終了する
  • 条件演算子:条件式の真偽によって片方の式のみを実行して処理を終了する

正直短絡評価について知らなくてもなんとなく使えてはいましたし、今後もそこまで困らなかった気がしますが、勉強してみると今までより使いこなせそうな気がしてわくわくします。

そこで(?)演算子を使って暗号を作ってみました。
もう1文字目とアイコンから大体察しがついてしまうと思いますが、よかったら解いてみてださい。
いつかもっと難解な暗号を作ってみたいです。

const ango = `${'ファ'||'フィ'||''||'フェ'||'フォ'}
${'',''||''&&''&&''}
${!''||(''??(''||''||''))}
${''&&(''??(''||''||''))}`;

console.log(ango);

おしまい。

参考

演算子の優先順位 - JavaScript | MDN
論理積 (&&) - JavaScript | MDN
論理和 (||) - JavaScript | MDN
Null 合体演算子 (??) - JavaScript | MDN
オプショナルチェーン (?.) - JavaScript | MDN
条件 (三項) 演算子 - JavaScript | MDN
JavaScriptのショートサーキット評価 - 30歳からのプログラミング

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2