0

このコードを検討してください:

if (a) 
    doSomething(a)
else if (b)
    doSomething(b)
else
    doSomething(c)

これは、javascript 論理演算子を使用して次のように同等に書き換えることができます。

(a && doSomething(a) || (!a && b && doSomething(b)) || (!a && !b && doSomething(c)))

私はそれが本当に読みにくいことを知っていますが、これはどういうわけか以前のバージョンを最適化しますか?

演算子&&||実際の式の値を返すという事実のために、比較は少なくなりますか?

4

5 に答える 5

2

この質問はコメントでほとんど回答されていますが、いくつかのコメントを拡張して、追加の洞察を提供したいと思いました.

Frédéric Hamidiがコメントで指摘したように、優れた JavaScript JIT コンパイラは、おそらくコードの両方のサンプルを修正し、まったく同じ結果を生成します。ただし、最適化を提供する JIT コンパイラーがなく、代わりに、コマンドを順次実行するインタープリターがあるとします。

その場合、最初の例 ( if-elseif-else を使用) では、次の手順が実行されます (大まかなアセンブリの疑似コードで申し訳ありませんが、問題を示していると思います)。

 1. CMP a, 0 // compare value of a to zero
 2. JZ 5 // If comparison is zero (a and 0 are equivalent), jump to the address of the else-if (starts on 5) instruction
 3. CALL doSomething(a) // Not how you would pass a parameter in assembly, but we'll skip over that stuff as it is irrelevant
 4. JMP 10 // Jump to end line. We do not need to do other evaluations.
 5. CMP b, 0 // Compare value of b to zero
 6. JZ 9 // If comparison is zero, jump to the else instruction (line 9)
 7. CALL doSomething(b)
 8. JMP 10 // Jump to end line. We do not need to do other evaluations.
 9. doSomething(c) // Else, we do something to C
 10. RET // Return/exit. We are finished.

一方、コードの 2 番目のサンプル (ブール演算のみを使用するサンプル) のシーケンスを見てみましょう。

 1. CMP a, 0
 2. JZ 6 // Start of comparison #2
 3. CALL doSomething(a)
 4. CMP EAX, 0 // Let's assume the call to doSomething puts a result in EAX
 5. JNZ 23 // Jump to end if doSomething returned a "truthy" result. Line 23 is the function's return point
 6. NOT a // let's say this call puts the NOTed a in EDX register
 7. CMP EDX, 0
 8. JZ 14  // start of comparison #3
 9. CMP b, 0
 10. JZ 14 // start of comparison #3
 11. CALL doSomething(b)
 12. CMP EAX, 0
 13. JNZ 23 // Again, jumping to return if doSomething returned "truthy" value.
 14. NOT a
 15. CMP EDX, 0
 16. JZ 23
 17. NOT b
 18. CMP EDX, 0
 19. JZ 23
 20. CALL doSomething(c)
 21. CMP EAX, 0
 23. RET

サンプル コード #1 (if-elseif-else分岐を使用) の方が効率的である可能性が高いと言えます。繰り返しになりますが、質問のコメントで述べたように、優れた JIT コンパイラはおそらく両方のコード サンプルを同等の状態に最適化しますが、コードを最適化するためのコンパイラを使用せずにインタプリタのみを利用している場合は、2 番目のコードこの例では、チェックする条件と変数が増えるため、より多くの操作が必要になります。

注: ここでのアセンブリは決して 100% 正確ではなく、適切なアセンブリではなく実際には疑似コードです。各コードサンプルで実行する必要がある操作の数の違いを指摘するために、これを単に含めました。

于 2016-08-30T19:28:17.057 に答える
1

もっと考える必要があるのは読みやすさです。最適化 (速度、メモリ使用量など) と読みやすさの境界線を見つける必要があります。

多くの場合、これらのタイプの最適化は小さな結果 (マイクロ最適化) をもたらしますが、可読性を大幅に妨げます。

于 2016-08-30T18:35:36.467 に答える
1

trueこの例では、各評価部分をリターンまたは1like で区切ることができます。次にa、または のbいずれかcが真かどうかのチェックを終了します。

a && (evaluate(a), 1) || b && (evaluate(b), 1) || evaluate(c)

配列は真であるため、さらに短いバージョンは配列で機能します。

a && [evaluate(a)] || b && [evaluate(b)] || evaluate(c)
于 2016-08-30T18:47:54.903 に答える
0

調べる

 (a && doSomething(a) || (!a && b && doSomething(b)) || (!a && !b && doSomething(c)))

最適である可能性が低いことを示唆しています。

aが true であるがdoSomething、偽の値 (0、false、未定義、null) を返す場合を考えてみましょう。この場合a && doSomething(a)、 is false は式の評価を続行させますが、!a && bこれはすぐに false になります ( atrue は!afalse になります)。これにより、式の評価を続行し、 !a && !bこれも false になります。

したがって、たとえば、書き換えられたコードを盲目的に実行すると、テストを使用するコードが 1 回しかテストする必要がない場合aに、 が 3 回テストされる可能性があります。ifa

于 2016-08-30T23:36:02.973 に答える