javascript中的分支
考虑以下代码:javascript中的分支,javascript,interpretation,Javascript,Interpretation,考虑以下代码: if (a) doSomething(a) else if (b) doSomething(b) else doSomething(c) 我可以使用javascript逻辑运算符将其改写为: (a && doSomething(a) || (!a && b && doSomething(b)) || (!a && !b && doSomething(c))) 我知道它不是
if (a)
doSomething(a)
else if (b)
doSomething(b)
else
doSomething(c)
我可以使用javascript逻辑运算符将其改写为:
(a && doSomething(a) || (!a && b && doSomething(b)) || (!a && !b && doSomething(c)))
我知道它不是真正可读的,但这会以某种方式优化以前的版本吗
由于运算符
&&
和|
返回实际的表达式值,比较会更少吗?您需要更多考虑的是可读性。您需要找到优化(速度、内存使用等)和可读性之间的界限
通常,这些类型的优化会产生较小的结果(微观优化),但会严重影响可读性。您可以使用返回值
true
或1
来分隔每个评估部分,如本例所示。然后它结束检查a
、b
或c
中的一个是否为真
a && (evaluate(a), 1) || b && (evaluate(b), 1) || evaluate(c)
更短的版本适用于数组,因为数组是真实的
a && [evaluate(a)] || b && [evaluate(b)] || evaluate(c)
我很好奇,通过v8反汇编程序(
d8--print_code
)运行这两个函数(简化)
第一版:
--- Raw source ---
(a, b) {
if (a) {
return foo(a);
} else if (b) {
return bar(b);
}
}
--- Code ---
source_position = 47
kind = FUNCTION
name = x
Instructions (size = 284)
44 REX.W movq rax,[rbp+0x18]
48 call 0x235a8cb18560 ;; debug: statement 60
;; debug: position 64
;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 8)
53 REX.W testq rax,rax
56 jz 184 (0x235a8cb66518)
62 REX.W movq rcx,0x1d21fad2d781 ;; object: 0x1d21fad2d781 <String[3]: foo>
72 REX.W movq rdx,[rsi+0x27]
76 call 0x235a8cb368c0 ;; debug: statement 77
;; debug: position 84
;; code: contextual, LOAD_IC, UNINITIALIZED
81 push rax
82 REX.W movq r10,0x144d04104121 ;; object: 0x144d04104121 <undefined>
92 push r10
94 push [rbp+0x18]
97 REX.W leaq rdx,[r12+r12*2]
101 REX.W movq rdi,[rsp+0x10]
106 call 0x235a8cb15d80 ;; code: CALL_IC, DEFAULT
111 REX.W movq rsi,[rbp-0x8]
115 REX.W addq rsp,0x8
119 REX.W movq rbx,0x7ed0fc04b11 ;; object: 0x7ed0fc04b11 Cell for 6144
129 addl [rbx+0xb],0xd1
133 jns 166 (0x235a8cb66506)
135 push rax
136 call InterruptCheck (0x235a8cb3ac60) ;; code: BUILTIN
141 pop rax
142 REX.W movq rbx,0x7ed0fc04b11 ;; object: 0x7ed0fc04b11 Cell for 6144
152 REX.W movq r10,0x180000000000
162 REX.W movq [rbx+0x7],r10
166 REX.W movq rsp,rbp ;; debug: statement 141
;; js return
169 pop rbp
170 ret 0x18
173 int3
174 int3
175 int3
176 int3
177 int3
178 int3
179 jmp 266 (0x235a8cb6656a)
184 REX.W movq rax,[rbp+0x10]
188 call 0x235a8cb18560 ;; debug: statement 103
;; debug: position 107
;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 24)
193 REX.W testq rax,rax
196 jz 266 (0x235a8cb6656a)
202 REX.W movq rcx,0x1d21fad2d7a1 ;; object: 0x1d21fad2d7a1 <String[3]: bar>
212 REX.W movq rdx,[rsi+0x27]
216 call 0x235a8cb368c0 ;; debug: statement 120
;; debug: position 127
;; code: contextual, LOAD_IC, UNINITIALIZED
221 push rax
222 REX.W movq r10,0x144d04104121 ;; object: 0x144d04104121 <undefined>
232 push r10
234 push [rbp+0x10]
237 xorl rdx,rdx
239 REX.W leaq rdx,[rdx+r12*4]
243 REX.W movq rdi,[rsp+0x10]
248 call 0x235a8cb15d80 ;; code: CALL_IC, DEFAULT
253 REX.W movq rsi,[rbp-0x8]
257 REX.W addq rsp,0x8
261 jmp 119 (0x235a8cb664d7)
266 REX.W movq rax,[r13-0x58]
270 jmp 119 (0x235a8cb664d7)
275 nop
这个问题在评论中已经得到了很好的回答,但我想对一些评论进行扩展,并提供一些额外的见解 正如注释中指出的,一个好的JavaScript JIT编译器可能会修复代码的两个示例,并产生完全相同的结果。但是,让我们假设没有JIT编译器提供优化,而是假设您只有一个按顺序执行命令的解释器 在这种情况下,在第一个示例中(使用if-elseif-else),您将执行以下步骤(请原谅粗汇编伪代码,但我认为它说明了问题): 另一方面,让我们看看第二个代码示例(仅使用布尔运算的示例)的序列: 我将继续说,示例代码#1(使用
if elseif else
分支)可能更有效。同样,正如在您的问题的评论中所提到的,一个好的JIT编译器可能会将两个代码示例优化为等效状态,但是如果您仅使用解释器,而没有任何类型的编译器来优化代码,那么第二个示例中的代码将需要更多的操作,因为需要检查更多的条件和变量
注意:这里的程序集绝对不是100%准确的,它实际上是伪代码,而不是正确的程序集。我只是将其包括在内,以指出每个代码示例必须执行的操作数量的差异。查看
(a && doSomething(a) || (!a && b && doSomething(b)) || (!a && !b && doSomething(c)))
这表明它不太可能是最优的
假设a
为真,但doSomething
返回一个假值(0,假,未定义,空)。在这种情况下,a&&doSomething(a)
为假,使表达式计算继续进行!a&&b
立即为假(a
为真表示!a
为假),使表达式计算继续进行
!a&&!b
这也是错误的
例如,盲目执行重写代码会导致<代码> A >代码>使用代码>代码>三次,如果测试只需要测试<代码> A<代码>一次。你应该考虑的一个主要因素是可读性。我已经编程多年了,我不得不考虑你的表达式会做什么。if/else if/else很明显。代码不仅仅是关于微观优化。你必须能够阅读它,其他在你之后的人必须能够阅读它。在这样一行中调试也会更加困难。还有一个很好的机会,就是你会错过一个括号,或者其他一些需要花费很长时间才能解决的简单错误。任何值得一试的JavaScript JIT编译器都会从你发布的两个表单中生成相同的代码。如何减少比较?在第二个例子中似乎还有更多,为什么不呢<代码>剂量测定法(a | | b | c)代码>我在可读性方面没有问题,我通常不会在生产环境中编写这样的代码,我只是想了解javascript@ampawd,然后您的JS实现开始发挥作用——使用纯解释器,两种形式之间的速度可能会有(轻微)差异,但是,正如我上面所说,使用JIT编译器,优化器将消除两者之间的语法差异。生成的机器代码将完全相同。这是什么语法?这在ES6中是新的吗?是的,应该是
(evaluate(a),true)
,你不同意吗@Nina?@mash,这是逗号运算符的用法。@FrédéricHamidi,任何真实值都适用。@NinaScholz啊,很有趣,只是从来没有见过它用这种方式。创意。哦,我太恨你了。哈哈,你打败了我,让我发布了一个关于这样一件事在程序集级别上需要执行的操作数量的答案:p我很高兴其他人考虑到了这一点,而且+1因为你在回答中包含了正确的程序集。你能为简单的doSomething做同样的事情吗(a | b | c)
?噢,我没有意识到它与其他两个版本不可比,这两个版本确实为a
和b
调用不同的函数,并且没有c
。除此之外,生成的代码似乎与第一个代码片段非常相似。@Bergi:请注意,每个布尔操作推送总是有两个跳转rax-调用_BOOLEAN-testq-jz-pop rax-jmp
,而if
只生成一个。
--- Raw source ---
(a, b, c) {
return foo(a || b || c);
}
--- Code ---
source_position = 220
kind = FUNCTION
name = z
Instructions (size = 228)
44 REX.W movq rcx,0x36f71e52d781 ;; object: 0x36f71e52d781 <String[3]: foo>
54 REX.W movq rdx,[rsi+0x27]
58 call 0x117d9bb368c0 ;; debug: statement 236
;; debug: position 243
;; code: contextual, LOAD_IC, UNINITIALIZED
63 push rax
64 REX.W movq r10,0xb18ce704121 ;; object: 0xb18ce704121 <undefined>
74 push r10
76 REX.W movq rax,[rbp+0x20]
80 push rax
81 call 0x117d9bb18560 ;; debug: position 247
;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 19)
86 REX.W testq rax,rax
89 jz 101 (0x117d9bb66945)
95 pop rax
96 jmp 109 (0x117d9bb6694d)
101 REX.W addq rsp,0x8
105 REX.W movq rax,[rbp+0x18]
109 push rax
110 call 0x117d9bb18560 ;; debug: position 252
;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 15)
115 REX.W testq rax,rax
118 jnz 131 (0x117d9bb66963)
124 REX.W addq rsp,0x8
128 push [rbp+0x10]
131 REX.W leaq rdx,[r12+r12*2]
135 REX.W movq rdi,[rsp+0x10]
140 call 0x117d9bb15d80 ;; code: CALL_IC, DEFAULT
145 REX.W movq rsi,[rbp-0x8]
149 REX.W addq rsp,0x8
153 REX.W movq rbx,0xdf65a404b41 ;; object: 0xdf65a404b41 Cell for 6144
163 addl [rbx+0xb],0xd1
167 jns 200 (0x117d9bb669a8)
169 push rax
170 call InterruptCheck (0x117d9bb3ac60) ;; code: BUILTIN
175 pop rax
176 REX.W movq rbx,0xdf65a404b41 ;; object: 0xdf65a404b41 Cell for 6144
186 REX.W movq r10,0x180000000000
196 REX.W movq [rbx+0x7],r10
200 REX.W movq rsp,rbp ;; debug: statement 261
;; js return
203 pop rbp
204 ret 0x20
207 int3
208 int3
209 int3
210 int3
211 int3
212 int3
213 REX.W movq rax,[r13-0x58]
217 jmp 153 (0x117d9bb66979)
222 nop
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.
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
(a && doSomething(a) || (!a && b && doSomething(b)) || (!a && !b && doSomething(c)))