【问题标题】:Unary operators "++" and "--" weird situation一元运算符“++”和“--”怪异情况
【发布时间】:2014-01-20 13:36:11
【问题描述】:

下面是一个使用一元运算符“++”的测试情况:

var j = 0 ;
console.log(j);
j = j++;
console.log(j);

为此,输出为:

0
0

由于 ++ 运算符的位置在操作数的后面,所以它的优先级低于赋值的优先级,我希望“j”首先接收自身的值(即 0),然后递增。那么为什么第二个console.log(j) 调用仍然显示“0”?

为了清楚起见,我知道解决方案是:

// 1)
j++;
// 2)
++j;
// 3)
j += 1;
// 4)
j = ++j;

但我需要知道为什么在这种特定情况下不执行增量步骤,而不是如何解决它!

【问题讨论】:

  • 我认为在这里快速回答的人没有得到您的要求,我同意您的观点,这令人困惑。我也希望它是 1。
  • 请注意,j = j++ 是 C 和 C++ 中未定义的行为,这是有充分理由的。
  • 没有理由对分配给同一变量的表达式中的变量使用一元运算符 (++)。
  • @Cruncher:在 SO 上寻找代表似乎是一个很好的理由!
  • 您将 precedence 与 order of evaluation 混淆了。请记住,它们彼此之间没有任何关系。当您说d = A() + B() * C(); 时,B() * C() 在A() 之前被评估是NOT的情况,因为乘法的优先级更高!相反,这与a = A(); b = B(); c = C(); t = b * c; d = a + t; 相同。乘法发生在加法之前,但这不会改变计算 操作数 的顺序。它们是从左到右计算的。

标签: javascript unary-operator


【解决方案1】:

使用后增量时,这是一种不直观(但不是“奇怪”!)的行为。

声明j = j++ 这样做:

  1. 评估 LHS
    • 在这种情况下,没有什么特别的事情发生,因为您只是命名了一个变量,但情况可能并非总是如此,例如foo() = j++
  2. 评估 RHS
    • 取j(0)的当前值,记为结果;
    • 递增j(得到1);
  3. 将 RHS 分配给 LHS
    • 回想一下,RHS 的计算结果是“记忆”值j (0)。

结果是无操作。

这里的关键是评估整个 RHS 并执行后增量,在最终分配之前。


http://www.ecma-international.org/ecma-262/5.1/#sec-11.3.1
http://www.ecma-international.org/ecma-262/5.1/#sec-11.13.1

【讨论】:

  • @AndreiOniga: j 在两种情况下获取增量值;但是,在场景 #1 中,您随后会立即重新分配原始值。回想一下var j = 0; alert(j++); alert(j); 弹出0 然后1,而不是1 然后1。
  • 请注意,如果左侧本身有副作用,则该副作用发生在右侧评估之前。
  • @EricLippert: *nods* 为了完整起见,我可以在步骤列表的最开始包含一个“Evaluate the LHS”步骤。在这种情况下,我忽略了它,尽管我可能不应该这样做。 (编辑:更好?:P)
  • 请注意,有些人使用“no-op”来描述不执行实际工作的汇编语言结构(也称为“NOP”)。我完全同意这是实际上无操作,它实际上不是汇编语言意义上的“无操作”。 (除非你的编译器非常聪明并且优化了...... :-)
  • @Brandon:我不同意。函数返回东西。 j++ 不是函数。这是关于评估的。
【解决方案2】:

根据ECMA Specifications for Postfix Increment Operator,

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

因此,很明显,新值首先在lhs 上设置(在本例中为j),旧值作为表达式的结果返回,在@987654324 中再次设置@。所以,值不会改变。

【讨论】:

  • 相关点是PutValue (#5) 在后增量完成评估之前执行之前。这与 C 和 C++ 形成对比,其中 PutValue 的等价物在下一个“序列点”执行,并且在序列点之前的其他地方使用相同的操作数是未定义的行为,因此被禁止。
  • @user4815162342:似乎后增量发生在 #4 中,但它实际上在 #6 中返回 oldValue。除非我误解了你的意思。
  • @Brian:没错。好吧,真正的重点是所有这些都发生在封装赋值操作完成其工作之前。
  • @LightnessRacesinOrbit:是的,所以这里的要点是问问自己作为程序员的预期行为是什么。我的猜测是在这种情况下,意图实际上是j += 1。
  • @Brian #4 只计算新值,#5 将其存储在j 中。注意这里的“LHS”是指内部表达式j++的操作数,而不是外部赋值j = ...的LHS。外部赋值接收旧值(根据 #6)并将 j 重置回 0。然而,在 C 中,后递增运算符的工作方式不同,并且根据“序列点”定义,因此在 C 中递增可能在外部赋值之后执行。为避免这种混淆,C 和 C++ 考虑引用和递增同一对象的“未定义”表达式。
【解决方案3】:

++ 所在的位置让您知道在那一刻您将从中获得什么价值

++j; // increment the value and then give it to me.
j++; // give me the value and then increment it.

你说的是

j = j++;

将 j 设置为 j 递增之前的值。

【讨论】:

    【解决方案4】:

    j = j++ 表示先将 RHS 值分配给 LHS,然后将 RHS 加 1。另一方面,j = ++j 表示将 RHS 加 1,然后将其分配给 LHS

    【讨论】:

      【解决方案5】:

      j++ 表示先赋值,后递增

      ++j 表示先递增,再赋值

      所以,

      var j = 0 ;
      console.log(j);
      j = ++j;
      console.log(j);
      
      0
      1
      

      【讨论】:

      • 显然,OP 意识到了这一点。并且 JS 中没有 := 运算符。
      • 这唯一的错误。我最近在 Pascal 上帮助了我的朋友:)
      • 正如 J0HN 指出的那样,我不是在寻求解决方案,而是寻求对这种相当不直观的行为的解释。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-17
      • 1970-01-01
      • 2021-12-07
      • 2011-12-03
      • 2010-11-21
      相关资源
      最近更新 更多