从编译过程分析a+++a与a+a++两者的差异

164 阅读3分钟

在运算符中,对于 ++x 和 x++ 我们是比较熟悉的,两者区别在于是先自加还是后自加。具体范例如下:

a = 2 b = ++a

b赋值得到的是前置a自加1后的结果,输出为3

a = 2 b = a++

b赋值得到的是后置a自加1前的结果,输出为2

但是上述两者不管前置还是后置a最终的值都是为3

如果是一个既有前置又有后置的表达式,计算结果就要按照运算符的优先级计算,我们以MDN上的优先级汇总表为例,权重越高,则代表优先级越高

优先级运算符类型结合性运算符
19分组n/a(不相关)( … )
18成员访问从左到右… . …
需计算的成员访问从左到右… [ … ]
new(带参数列表)n/anew … ( … )
函数调用从左到右… ( … )
可选链(Optional chaining)从左到右?.
17new(无参数列表)从右到左new …
16后置递增n/a… ++
后置递减… --
15逻辑非 (!)从右到左! …
按位非 (~)~ …
一元加法 (+)+ …
一元减法 (-)- …
前置递增++ …
前置递减-- …
typeoftypeof …
voidvoid …
deletedelete …
awaitawait …
14幂 (**)从右到左… ** …
13乘法 (*)从左到右… * …
除法 (/)… / …
取余 (%)… % …
12加法 (+)从左到右… + …
减法 (-)… - …
11按位左移 (<<)从左到右… << …
按位右移 (>>)… >> …
无符号右移 (>>>)… >>> …
10小于 (<)从左到右… < …
小于等于 (<=)… <= …
大于 (>)… > …
大于等于 (>=)… >= …
in… in …
instanceof… instanceof …
9相等 (==)从左到右… == …
不相等 (!=)… != …
一致/严格相等 (===)… === …
不一致/严格不相等 (!==)… !== …
8按位与 (&)从左到右… & …
7按位异或 (^)从左到右… ^ …
6[按位或 ()](developer.mozilla.org/zh-CN/docs/…)从左到右`……`
5逻辑与 (&&)从左到右… && …
4[逻辑或 ()](developer.mozilla.org/zh-CN/docs/…)从左到右`……`
空值合并 (??)从左到右… ?? …
3条件(三元)运算符从右到左… ? … : …
2赋值从右到左… = …
… += …
… -= …
… **= …
… *= …
… /= …
… %= …
… <<= …
… >>= …
… >>>= …
… &= …
… ^= …
`…= …`
… &&= …
`…= …`
… ??= …
1逗号 / 序列从左到右… , …

从汇总表中可以看出后置的优先级是要高于前置的,由此下面的表达式需要通过优先级计算

let a = 2, b =3
console.log(a+++b)

通过优先级我们可以将a+++b表达式看成(a++)+b,得到的结果为5

上述表达式是两个不同的变量,而对于一个变量的前置后置计算,则需要从编译器元素的入栈和出栈顺序去分析

对于下面代码,输出结果是多少?

let a = 1
console.log(a+++a)

对于下面代码,输出结果又是多少?

let a = 1
console.log( a+a++ )

以运算符的优先级来看,a+++a等同于(a++)+a,而 a+a++等同于a+(a++),然而两者的结果为什么又不一样?
此时可以从编译过程去看

a+++a的编译过程如下:

     +
    /   \
   /     \
  a++     a

编译器的处理过程:
1. push(a++), 即push(1),之后 a自增变成2
2. push(a),即push(2)
3. result = pop()+pop(), 出栈两个操作数(1,2)相加,得到3

a+a++的编译过程如下:

    +
    /   \
   /     \
 a        a++

编译器的处理过程:
1. push(a), a入栈(1)
2. push(a++),即push(1),之后 a自增变成2
3. result = pop()+pop(), 出栈两个操作数(1,1)相加,得到2

从编译过程的分析我们可以看到元素的入栈出栈也可以是带计算符的表达式
资料来源:饥人谷