JavaScript 中带有运算符的变量链式赋值

Chained assignment of variables with operators in JavaScript

本文关键字:变量 赋值 运算符 JavaScript      更新时间:2023-09-26

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

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

JSFiddle在这里。

  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:权威指南(第 6 版)中寻找过为什么这可能(特别是在 4.11.1 "Assignment with operation"下),但在那里什么也没找到。克罗克福德似乎也没有在《好的部分》中明确提到这一点。我使用了各种搜索词来尝试查找有关此行为的更多信息。谁能告诉我这种现象叫什么,或者指出我一些关于这种行为的信息(假设它是预期的)或我可能做错了什么(假设它不是)?

<小时 />

铌。我知道示例 3 中的括号可能是多余的,因为据我了解,赋值优先级无论如何都是从右到左的。但我认为有他们在那里会让这个例子更容易谈论。

<小时 />

更新

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

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

a op= b

其中 op 是运算符,等效于表达式:

a = a op b

在第一行中,表达式 a 的计算结果为一次。在第二个中,它被评估两次。仅当a副作用(如函数调用或增量运算符)包含副作用时,这两种情况才有所不同。例如,以下两个作业并不相同:

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

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

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

恕我直言,我认为弗拉纳根使这变得混乱,它似乎与 ECMAScript 约定中的内容相矛盾(如下面由 pocka 粘贴),但这可能是我的阅读/误解。他说的是不正确的还是不清楚的?或者,只有我一个人?

我认为

(不确定,尽管这是违反直觉的)你可以想象:

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

被写成:

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

尽管加号+运算符具有从右到左的关联性,但 JavaScript 表达式是从左到右计算的,因此首先计算a现在9,然后(b += (a += b))评估为13

现在+运算符从右到左加法,从而将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),然后评估y2。现在再次计算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现在这个值被求和已经评估的值,即 9 导致22

如果不是这样发生的,这意味着=左侧的a将被评估两次,但事实并非如此。

摘要:您无法更新已计算表达式的值。

我希望这可以为您清除这一点,如果您有任何其他问题,请随时提出:)

根据 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. 如果满足以下条件,则引发语法错误异常:Type(lref) is Reference is true IsStrictReference(lref) is true Type(GetBase(lref)) is Environment Record
    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