【问题标题】:Pre increment in JavascriptJavascript中的预增量
【发布时间】:2014-07-18 19:12:59
【问题描述】:

我刚刚在 Javascript 中遇到了一个关于预增量的“功能”。在我使用过的所有其他语言中,它就像我想象的那样。例如。在 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 并且不引用变量。这是故意的吗?如果是,为什么?

【问题讨论】:

标签: javascript pre-increment


【解决方案1】:

来自 EcmaScript 标准:

11.4.4 前缀自增运算符

产生式 UnaryExpression : ++ UnaryExpression 的评估如下:

  1. 设 expr 为计算 UnaryExpression 的结果。
  2. 如果以下条件都为真,则抛出 SyntaxError 异常: �
    • Type(expr) is Reference is true
    • IsStrictReference(expr) 为真
    • Type(GetBase(expr)) 是环境记录
    • GetReferencedName(expr) 是“eval”或“arguments”
  3. 让 oldValue 为 ToNumber(GetValue(expr))。
  4. 使用与 + 运算符相同的规则(参见 11.6.3),让 newValue 为将值 1 与 oldValue 相加的结果。
  5. 调用 PutValue(expr, newValue)。
  6. 返回新值。

11.13.2 复合赋值 (op=)

产生式AssignmentExpression : LeftHandSideExpressionAssignmentOperatorAssignmentExpression,其中AssignmentOperator为@=,@表示上述运算符之一,计算如下:

  1. 令 lref 为计算 LeftHandSideExpression 的结果。
  2. 设 lval 为 GetValue(lref)。
  3. 设 rref 为评估 AssignmentExpression 的结果。
  4. 设 rval 为 GetValue(rref)。
  5. 令 r 为将运算符 @ 应用于 lval 和 rval 的结果。
  6. 如果以下条件都为真,则抛出 SyntaxError 异常:
    • Type(lref) is Reference is true
    • IsStrictReference(lref) 为真
    • Type(GetBase(lref)) 是环境记录
    • 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。或者它可能会使您的计算机着火。所有这些都是符合标准的编译器。因此,大多数人会建议您只在语句中使用一次前/后自增变量。

【讨论】:

    【解决方案2】:

    我相信这是因为 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

    【讨论】:

    • 第三个选项是执行++i(即i=1),然后执行i+=i(此时为i=1+1)。这导致2
    • 我看不出这是一个有效的选项,好像两个赋值都在同一个语句中,在语句的入口处 i 的原始值为零。其中一个分配必须优先,另一个必须使用原始值。如果代码是这样编写的,我可以看到它会是 2:var i = 0; ++i; i += i;
    • 这种工作方式是在编译器/解释器中在解析语句中的任何其他内容之前解析预增量,因此i+=++i 确实等同于++i; i+=i。正如我在回答中所展示的那样,这不符合 EcmaScript 规范,而是 C++ 编译器的有效选项,正如 OP 清楚地表明的那样。
    • 我明白你在说什么,我想我只是假设语言的语法在计算复合赋值时会使用类似于运算符优先级的东西。我可以看到 OP 正在成为一种 ref 与 value 类型的问题。考虑到这一点,我可能会修改我的评论/答案,以将 JS 处理原语称为不可变。
    • 在 EcmaScript 中确实如此。请参阅我对确切机制的回答。在 C++ 中,规范编写者基本上持有这样一种态度:“如果你愚蠢到在一个语句中使用两次修改前/修改后的变量,那么你应该得到你所得到的一切。” (具体来说,Spec 表示“在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。”)。 i+=++i 修改了两次 i,因此发生的事情不在 C99 标准中。
    猜你喜欢
    • 1970-01-01
    • 2015-05-03
    • 1970-01-01
    • 2015-11-13
    • 2010-10-03
    相关资源
    最近更新 更多