JavaScript 中带有运算符的变量链式赋值
Chained assignment of variables with operators in JavaScript
我想使用运算符快速连续地对几个变量做一些事情。我不认为我想做的事情本身很重要;我的问题更多的是关于JavaScript评估的基础知识。
在下面的三个示例中,我尝试使用加法来更改两个变量的值。然而,并非所有人都像我(也许是天真地)预期的那样表现。
JSFiddle在这里。
-
作为三个单独语句的操作
var a = 9, b = 2; a += b; b += a; a += b; // a === 24, b === 13
-
用逗号运算符分隔的运算
var a = 9, b = 2; a += b, b += a, a += b; // AS EXPECTED: a === 24, b === 13
-
一个语句/表达式中的操作
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
我认为这意味着我的一行示例应该产生与其他两个示例相同的结果,因为:
- 弗拉纳根提到了
a = a op b
中发生的两个评估,而不是一个,这意味着这实际上与a op= b
不同,其中a
没有被评估为右边的lval
。
我 - 假设我使用的赋值运算符(例如
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)
,然后评估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
,现在这个值被求和已经评估的值,即 9 导致22
。
如果不是这样发生的,这意味着=
左侧的a
将被评估两次,但事实并非如此。
摘要:您无法更新已计算表达式的值。
我希望这可以为您清除这一点,如果您有任何其他问题,请随时提出:)
根据 ECMA-262 第 5 版 11.13.2,复合赋值运算符的计算如下:
- 让 lref 成为评估 LeftHandSideExpression 的结果。
- 让 lval 成为 GetValue(lref)。
- 让 rref 成为评估 AssignmentExpression 的结果。
- 让 rval 成为 GetValue(rref)。
- 设 r 是将运算符 @ 应用于 lval 和 rval 的结果。
- 如果满足以下条件,则引发语法错误异常:Type(lref) is Reference is true IsStrictReference(lref) is true Type(GetBase(lref)) is Environment Record
GetReferencedName(lref) 是 "eval" 或 "arguments"- 调用 PutValue(lref, r)。
- 返回 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 === 22
,b === 13
。
- Javascript变量赋值|
- 如何在javascript中为全局变量赋值
- 我可以在javascript中的回调函数中为变量赋值吗
- JavaScript 变量赋值的行为令人惊讶
- 为什么 Javascript 在变量赋值时将 null 转换为字符串
- JavaScript: 变量赋值 in for 语句 |在前面或里面
- 我可以将变量赋值为数组值吗?
- 变量赋值无效
- 一行中的多个变量赋值
- Javascript中的多变量赋值
- 如何根据 IF 句子和警报为变量赋值?.JS
- 无法为变量赋值 - Javascript
- 为所有以特定字符串开头的变量赋值
- Javascript中多变量赋值的正确方法
- 条件变量赋值的最佳方法
- 在 JavaScript 中是否有一个严格等价的 ||在变量赋值中使用时
- 在 JavaScript 中,未声明的变量赋值可以在函数范围之外访问
- 使用Javascript,变量赋值上下文中的表达式与上下文外的表达式有什么区别
- JavaScript 变量赋值
- 当我为 PHP 变量赋值时,是否可以调用 JavaScript 函数