【问题标题】:Chained assignment of variables with operators in JavaScript在 JavaScript 中使用运算符对变量进行链式赋值
【发布时间】:2013-11-03 14:57:38
【问题描述】:

我想快速连续地使用运算符对几个变量做一些事情。我不认为我想做什么本身很重要;我的问题更多是关于 JavaScript 评估的基础知识。

在下面的三个例子中,我尝试使用加法来改变两个变量的值。然而,并不是所有的表现都像我(也许是天真的)预期的那样。

JSFiddle here.

  1. 三个单独的语句的操作

    var a = 9, b = 2;
    a += b; b += a; a += b;
    // a === 24, b === 13
    
  2. 用逗号分隔的操作

    var a = 9, b = 2;
    a += b, b += a, a += b;
    // AS EXPECTED: a === 24, b === 13
    
  3. 一个语句/表达式中的操作

    var a = 9, b = 2;
    a += (b += (a += b)); 
    // BUT HERE WE GET THIS: a === 22, b === 13
    

在最后一个示例中,b 的计算结果符合预期,但 a 的计算结果比前两个示例中的少二。

我认为这是因为括号中的所有内容都返回正确的值,但最终添加到a 的原始值,即9,而不是(a += b) 之前建议的优先级值11.

我已经在 Flanagan 的 JavaScript: The Definitive Guide(第 6 版)(特别是在 4.11.1“操作分配”下)中寻找了为什么这可能会出现,但在那里一无所获。 Crockford 似乎也没有在 The Good Parts 中明确提及它。我使用了各种搜索词来尝试查找有关此行为的更多信息。谁能告诉我这种现象叫什么或指出一些关于这种行为的信息(假设它是预期的)或我可能做错了什么(假设不是)?


注意。我意识到示例 3 中的括号可能是多余的,因为据我所知,赋值优先级无论如何都是从右到左的。但我认为将它们放在那里会使这个例子更容易讨论。


更新

从下面的答案来看,我认为我对这个问题的困惑实际上源于从弗拉纳根的书中吸收了几段,可能是错误的:

在大多数情况下,表达式:

a op= b

其中op是一个运算符,等价于表达式:

a = a op b

在第一行,表达式a 被计算一次。在第二个中,它被评估两次。仅当 side a 包含诸如函数调用或增量运算符之类的副作用时,这两种情况才有所不同。例如下面的两个赋值是不一样的:

data[i++] *= 2
data[i++] = data[i++] * 2

我认为这意味着我的一行示例应该产生与其他两个相同的结果,因为:

  1. Flanagan 提到了在 a = a op b 中发生的两个评估,而不是一个,这意味着这实际上与 a op= b 不同,其中 a 没有被评估为右侧的 lval
  2. 我假设我使用的赋值运算符(例如a += b)会被视为副作用。

恕我直言,我认为 Flanagan 让这变得令人困惑,它似乎与 ECMAScript 约定中的内容相矛盾(由pocka 粘贴在下面),但这可能是我的阅读/误解。他说的是不正确还是不清楚?还是只有我一个人?

【问题讨论】:

  • 用手写数学写出来,用铅笔会得到相同的结果....真的认为你回答了自己的问题
  • @charlietfl 示例 1 和 2 是示例 3 的简易版本。如果第一次评估是 a + b 而不是 a += b,我可以理解结果 a === 22。我的问题是关于行为,因为这是对a 的分配,在我们进行最终分配/评估时似乎已经忘记了。
  • 括号的从右到左的优先级使您可以将a 添加到括号中评估的内容中,而前面的两个示例将b 添加到a...然后在右侧添加相同的amont。没有什么时髦的。我认为这是你认为逻辑有缺陷并想深入研究原因的那些心灵融化的事情之一......当它是你自己的思想在欺骗你时。我们都去过那里
  • @charlietfl 我喜欢“你自己的想法在欺骗你”的部分。
  • @charlietfl 你对我自己的想法可能是正确的。我已经更新了这个问题(希望)解释我的困惑。感谢您说“我们都去过那里”——这实际上令人欣慰:-)

标签: javascript operators variable-assignment


【解决方案1】:

我认为(不确定,虽然这违反直觉)你可以想象:

a += (b += (a += b));

写成:

a = a + (b += (a += b));

虽然加号 + 运算符具有从右到左的关联性,但 JavaScript 表达式是从左到右计算的,所以首先计算 a,现在是 9,然后 (b += (a += b)) 计算为 @987654327 @。

现在+ 运算符从右到左添加,因此将13 添加到9 并得到22

编辑:我不会直接评论你的问题,因为我读了它们会感到困惑:)。

相反,我将尝试以不同的方式解释这一点。我认为您混淆的主要原因来自运算符优先级、关联性和评估顺序之间的差异。

我真的建议你阅读关于评估顺序的部分(书中的 4.7.7,顺便说一句,这是一本很棒的书)

我们先来看一个例子:

var x =1, y = 2, z = 3;
var alpha = (z=4) + y * z;
console.log(x); // 1
console.log(y); // 2
console.log(z); // 4
console.log(alpha);  // 12

在本例中,虽然乘法运算符*的优先级高于求和运算符+,但整个表达式的不同分量的求值仍然是从左到右的。

左边的alpha首先被声明和创建,然后(z=4)被评估,然后y被评估为2。现在z 再次被求值,结果为4,注意这是新值,它是由表达式前面将4 分配给z 的副作用引起的,记住(z=4)

这导致alpha 的总体值等于12

现在回到我们原来的表达方式:

a += (b += (a += b));

首先评估左侧的a,现在是9,然后评估左侧的第一个b,现在是2,现在评估第二个a,即9此外,然后评估右侧的最后一个 b,它又是 2

现在开始真正的工作,因为括号中最后一个(a += b)被评估所以现在我们有a = 11,然后(b += (a += b))被评估现在是13现在这个value 与 已经 评估的值相加,即 9 得出22

如果没有发生这种情况,这意味着= 左侧的a 将被评估两次,但事实并非如此。

总结:您无法更新已评估的表达式的值。

我希望这可以为您解决这个问题,如果您有任何其他问题,请随时提问:)

【讨论】:

  • @charlietfl 谢谢,错误已更正。它们实际上是 13 而不是 11。对不起这个。
  • @charlietfl 不错的一个 xD
  • @Sniffer +1 为您解答。我不知道它是否正确,但你让我以不同的方式思考它。你能看看我的问题中的更新,看看你的想法吗?你认为弗拉纳根在这方面有误导还是只是我?谢谢。
【解决方案2】:

根据 ECMA-262 第 5 版。 11.13.2、复合赋值运算符的求值如下:

  1. 设 lref 为计算 LeftHandSideExpression 的结果。
  2. 令 lval 为 GetValue(lref)。
  3. 设 rref 为评估 AssignmentExpression 的结果。
  4. 令 rval 为 GetValue(rref)。
  5. 令 r 为将运算符 @ 应用于 lval 和 rval 的结果。
  6. 如果以下条件都为真,则抛出 SyntaxError 异常: Type(lref) 为 Reference 为真 IsStrictReference(lref) 为 true Type(GetBase(lref)) 是环境记录
    GetReferencedName(lref) 是“eval”或“arguments”
  7. 调用 PutValue(lref, r)。
  8. 返回 r.

最后一个例子的评估如下:

1. Let a be lref, Let (b += (a += b)) be rlef.
2. Evaluate a and Let lval be the result of it(9).
3. Evaluate (b += (a += b)) and Let rval be the result of it.
   a. Let b be lref_, Let (a += b) be rlef_.   
   b. Evaluate b and let lval_ be the result of it(2).
   c. Evaluate (a += b) and let rval_ be the result of it.
     A. Let a be lref__, let b be rlef.
     B. Evaluate a and Let lval__ be the result of it(9).
     C. Evaluate b and Let rval__ be the result of it(2).
     D. Put lval__ + rval__ (means 9+2) to lref__(a) and return it.   
   d. Put lval_ + rval_ (means 2+11) to lref_(b) and return it.
4. Put lval + rval (means 9+13) to lref(a) and return it.

那么我们可以得到a === 22b === 13

【讨论】:

  • 感谢您的回答@pocka,特别是为我的具体示例翻译 ECMA 标准。我已经更新了我的问题,以显示我认为我对此有误解的地方。我粘贴的 Flanagan 摘录是否与 ECMA 标准中的内容相矛盾?也许这只是误导......或者也许只是我。再次感谢。
猜你喜欢
  • 2015-02-08
  • 2014-01-08
  • 2022-08-17
  • 2017-12-06
  • 2012-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多