在 JavaScript 和 Python 中,/=
都是“增强赋值”运算符,含义几乎相同。
在 JS 中:
var i = 10;
i /= 2;
… 相当于:
var i = 10;
i = i / 2;
并且,在 Python 中:
i = 10
i /= 2
……同样等价于(不完全相同,但对于数字来说足够接近):
i = 10
i = i / 2
但是,有一个非常大的区别。
在 JavaScript 中,赋值是一个表达式——它有一个值,这个值就是赋给变量的值。所以:
var i = 10;
var j = i /= 2;
……大致相当于:
var i = 10;
i /= 2;
var j = i;
在 Python 中,赋值是一个语句。它没有值,也不能在表达式中使用。所以:
i = 10
j = i /= 2
... 提出一个SyntaxError
.
移植在表达式中间使用赋值(增强或其他方式)的代码通常需要将该表达式分成多行和/或找到一种方法来重写表达式以不需要任何赋值。(但通常,这不是一件坏事,因为原来的表达方式本来就不是很可读……)
例如,假设 JS 从左到右评估操作数(我不确定是否保证?):
def easeInQuad(x, t, b, c, d):
t /= d
return c*t*t+b
更一般地说,你这样做:
old_t = t
t /= d
然后您将t
之前的任何实例t/=d
替换为old_t
,并保留所有实例 fromt/=d
和 later 单独。幸运的是,在这种情况下,没有以前的实例,所以我们不需要那些old_t
东西。
而且,如果您考虑一下,您可以t
通过以下任何一种方式轻松获得相同的效果,而无需在一行中进行更改,并且更具可读性:
return c * (t/d) * (t/d) + b
return c * (t/d)**2 + b
return c * t*t / d*d + b
用 C 语言思考的人会立即抱怨这些都“太慢”。毕竟,第一个执行额外的除法,第二个执行取幂而不是乘法,第三个执行两次乘法而不是一次。恐怖!
当然,您始终可以使用临时变量:
t_over_d = t/d
return c * t_over_d * t_over_d + b
……但同样,对于 C 程序员来说,这意味着您正在用完一个有价值的寄存器。当然,在 1985 年之后编写的每个编译器都会t
在出现时立即检测到已死t_over_d
并重用相同的寄存器,但如果可以的话,为什么不强制它重用寄存器,特别是如果它也节省了几次击键呢?
在 JS 或 Python 中,乘法的成本只是调用函数和解释字节码等成本的一小部分,你甚至都不会注意到它。同时,重新绑定局部变量的成本(尤其是在 V8 风格或 PyPy 风格的 JIT 解释器中)可能比传递一个未命名的临时结果的成本要高得多。
因此,这是一个误导性“优化”的范例案例,使代码更难理解,同时可能会减慢而不是加快速度,并且在一个无论如何都不可能成为值得优化的瓶颈的领域。
由于 gnibbler 提出了 JavaScript 是否确实保证了这种评估顺序的问题……</p>
首先,JavaScript 被有效地定义为“Firefox 所做的事情”(和“Spidermonkey 所做的事情”,但这应该是同一件事——如果不是,那么 JavaScript 做了两件事,所以好两倍, 对?)。但是 ECMAScript是由标准定义的,而且每个 JS 实现(不管名称)都在口头上支持这些标准,我们可以假装 ECMAScript 5.1 是所有实现都遵循的标准(只要“所有实现"的意思是“歌剧”)。你可以在这里找到它。
因此,在 ES 5.1 中:11.5 乘法运算符保证 的结果(t/=d)
将在 之前评估t
,而11.13.2 复合赋值保证评估将在其完成之前t/=d
设置值。t
(您必须阅读“评估”GetValue
的含义和SetValue
含义,但我很确定这确实得到了保证。)