Javascript中的预增量运算符

11

我刚刚在JavaScript中遇到了一个关于pre-increments的“特性”。在我使用过的所有其他语言中,它都像我想象的那样。例如,在C++中:

#include <iostream>

int main()
{
    int i = 0;

    i += ++i;

    std::cout << i << std::endl; // Outputs 2.
}
因此,++i 不会复制变量,因此输出结果为2。
在PHP中相同:
<?php

$i = 0;

$i += ++$i;

echo $i; // Outputs 2.

然而,在JavaScript中:

var i = 0;

i += ++i;

console.log(i); // Outputs 1.

看起来在JavaScript中,它复制了i而不是引用变量。这是故意的吗?如果是,为什么?


也许不是一个解释,甚至不是一个解决方案,但肯定是一篇好文章:https://dev59.com/u3NA5IYBdhLWcg3wa9Gp - emerson.marini
在C++中,这甚至没有意义。它是未定义的行为。 - The Paramagnetic Croissant
另一篇不错的阅读材料:http://javascript.about.com/od/hintsandtips/a/pre-and-post-increment.htm - emerson.marini
https://dev59.com/inI_5IYBdhLWcg3wDOnW - Abdennour TOUMI
2个回答

7

来自 EcmaScript 标准:

11.4.4 前缀递增运算符

产生式 UnaryExpression : ++ UnaryExpression 的计算方法如下:

  1. 将 UnaryExpression 求值为 expr。
  2. 如果以下所有条件都为 true,则抛出 SyntaxError 异常:
    • Type(expr) 是 Reference 为 true。
    • IsStrictReference(expr) 为 true。
    • Type(GetBase(expr)) 是 Environment Record。
    • GetReferencedName(expr) 是 "eval" 或 "arguments" 中的一个。
  3. 将 oldValue 设为 ToNumber(GetValue(expr))。
  4. 使用与 + 运算符相同的规则,将值 1 加到 oldValue 上得到 newValue。
  5. 调用 PutValue(expr, newValue)。
  6. 返回 newValue。

还有

11.13.2 复合赋值 ( op= )

产生式 AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression,其中 AssignmentOperator 是 @=,@ 表示上述运算符之一,其计算方法如下:

  1. 将 LeftHandSideExpression 求值为 lref。
  2. 将 lref 的值求出并赋给 lval。
  3. 将 AssignmentExpression 求值,并将结果赋给 rref。
  4. 将 rref 的值求出并赋给 rval。
  5. 将 lval 和 rval 应用运算符 @,得到结果 r。
  6. 如果以下所有条件都为 true,则抛出 SyntaxError 异常:
    • Type(lref) 是 Reference 为 true。
    • IsStrictReference(lref) 为 true。
    • Type(GetBase(lref)) 是 Environment Record。
    • GetReferencedName(lref) 是 "eval" 或 "arguments" 中的一个。
  7. 调用 PutValue(lref, r)

因此,var i = 0; i += ++i 的结果是:

i = 0;
lvalue = value(i), which is 0;
rvalue = value(++i), which is: increment i, then value of i (1);
thus, rvalue = 1;
i = lvalue (0) + rvalue (1), which is 1.

完全按照规范执行。

然而,在C++中,这被明确定义为未定义行为,因此在另一个编译器上,您可能也会得到1或99。或者它可能会让您的计算机着火。所有这些都将是符合标准的编译器。因此,大多数人建议您仅在语句中使用一次预/后增量变量。


0

我认为这是因为 JavaScript 将 var i = 0; i += ++i; 理解为:

var i = 0;
++i = 1;
i += 1;

这是因为++i在执行+=之前将i变成1,所以结果是0 + 1 = 1

实际上,我越想越觉得,它还能做什么呢?

有两个赋值操作,其中一个必须在另一个之前完成。所以两个选项要么是:

  • 先执行++i,使i = i + 1,其中i从0开始等于1

或者

  • 先执行+=,使i = 0 + (++i),也等于1

1
第三个选项是执行 ++i(即 i=1),然后执行 i+=i(此时 i=1+1)。这将导致 2 - Amadan
我不认为这是一个有效的选项,因为如果两个赋值语句在同一语句中,那么进入该语句时i的原始值为零。其中一个赋值必须优先执行,而另一个则必须使用原始值。 如果代码像这样编写,我可以看到它将是2: var i = 0; ++i; i += i; - dougajmcdonald
这是通过编译器/解释器实现的,它们在解析语句中的任何其他内容之前先解析预增量,因此i+=++i确实等同于++i; i+=i。正如我在我的回答中所展示的那样,这不符合EcmaScript规范,但对于C++编译器而言,这是一种有效的选项,正如OP明确展示的那样。 - Amadan
我明白你的意思,我猜我只是认为语言的语法在解决复合赋值时会使用类似于操作符优先级的东西。 考虑到这一点,我可能会修改我的评论 / 回答,以便提到JS将基本类型视为不可变的。 - dougajmcdonald
在EcmaScript中确实如此。请参见我对确切机制的回答。在C++中,规范编写者基本上持有这样的态度:“如果你愚蠢到在语句中两次使用预/后修改变量,那么你就应该承担一切后果。”(具体而言,Spec说:“在前一个和下一个序列点之间,一个对象通过表达式的求值最多只能被修改一次。”)。i+=++i会修改i两次,因此其结果不在C99标准的覆盖范围内。 - Amadan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接