前言
最近看到了一道关于运算优先级的面试题,题目如下:
var a = 1;
b = a+++a
console.log(a, b)
要回答这道题,我们需要了解到JavaScript中的三个概念:优先级、结合性、运算顺序。
优先级
什么是优先级呢,以前我们学数学的时候,大家都知道这么一个问题,在一个数学表达式中,先算乘除,再算加减,这种先算哪个后算哪个叫做优先级。运算符的优先级决定了表达式中运算执行的先后顺序,优先级高的运算符最先被执行。
consol.log(1 + 2 * 3) // 1 + 6
// 输出数字7
a=3
console.log(++a == 3) // false
js中一元运算符优先级高于等号运算符,所以上面的例子先运算 ++a 为4 然后判断 4 == 3所以返回false
结合性
我们刚才说过在数学表达式中,先算乘除,再算加减,这种是有优先级的,那么乘和除它的优先级是相等的,当乘除同时存在时,先算乘还是先算除呢?这个是js的处理方式,我们把这个处理方式叫做结合性。严谨一些说就是当一个表达式,存在多个运算符,并且这几个运算符优先级相同时,这种如何计算?结合性决定了拥有相同优先级的运算符的执行顺序。js中存在两种结合性,一种是左结合 一种是右结合。其实就是从左开始计算还是从右开始计算。
例如下面的这个例子上得到布尔类型还是数字类型
var a = 5;
console.log(!a++)
-
如果这个我们从左到右计算的话,就会先计算 !a,得到一个布尔类型的数据,然后再进行++运算,最终会得到一个数字类型的数据。
-
如果我们从右到左计算的话,就会先计算a++,得到一个数字类型的数据,然后再进行!的运算,最终会得到一个布尔类型的数据。
!和++都属于一元运算符,当两个一元运算符同时出现时,从右向左运算,即右结合。所以上面的例子会得到一个布尔类型的数据。那么在给大家举个右结合的例子:
x = a ? b : c ? d : e ? f : g
上面的这个例子最终解析为:
x = a ? b : ( c ? d : ( e ? f : g ))
js中遇到三元运算符也是右结合的计算方式。
运算顺序
在js中虽然规定了优先级,也规定了结合性,那么有了优先级和结合性,就可以计算整个表达式,,这只是对一个表达式的计算,,当表达式里面还存在表达式的时候,也就是说这个表达式包含子表达式的时候,那这个时候js是怎么处理的呢?在js中所有的运算,如果这个里面存在表达式的话,那么js永远是从左向右的运行。这个是js的运算顺序。
汇总表
下面的表将所有运算符按照优先级的不同从高(20)到低(1)排列。
| 优先级 | 运算类型 | 关联性 | 运算符 |
|---|---|---|---|
| 21 | 圆括号 | n/a(不相关) | ( … ) |
| 20 | 成员访问 | 从左到右 | … . … |
| 需计算的成员访问 | 从左到右 | … [ … ] | |
| new (带参数列表) | n/a | … [ … ] | |
| 函数调用 | 从左到右 | … ( … ) | |
| 可选链(Optional chaining) | 从左到右 | ?. | |
| 19 | new (无参数列表) | 从右到左 | new … |
| 18 | 后置递增(运算符在后) | n/a | … ++ |
| 后置递减(运算符在后) | … -- | ||
| 17 | 逻辑非 | 从右到左 | ! … |
| 按位非 | ~ … | ||
| 一元加法 | + … | ||
| 一元减法 | - … | ||
| 前置递增 | ++ … | ||
| 前置递减 | -- … | ||
| typeof | typeof … | ||
| void | void … | ||
| delete | delete … | ||
| await | await … | ||
| 16 | 幂 | 从右到左 | … ** … |
| 15 | 乘法 | 从右到左 | … ** … |
| 除法 | … / … | ||
| 取模 | … % … | ||
| 14 | 加法 | 从左到右 | … + … |
| 减法 | … - … | ||
| 13 | 按位左移 | 从左到右 | … << … |
| 按位右移 | … >> … | ||
| 无符号右移 | … >>> … | ||
| 12 | 按位左移 | 从左到右 | … < … |
| 无符号右移 | … >>> … | ||
| 小于等于 | ... <= … | ||
| 大于 | ... > … | ||
| 大于等于 | ... >= … | ||
| in | ... in … | ||
| instanceof | ... instanceof … | ||
| 11 | 等号 | 从左到右 | ... == … |
| 非等号 | ... != … | ||
| 全等号 | ... === … | ||
| 非全等号 | ... !== … | ||
| 10 | 按位与 | 从左到右 | ... & … |
| 9 | 按位异或 | 从左到右 | ... ^ … |
| 8 | 按位或 | 从左到右 | ... | … |
| 7 | 逻辑与 | 从左到右 | ... && … |
| 6 | 逻辑或 | 从左到右 | ... || … |
| 5 | 空值合并 | 从左到右 | ... ?? … |
| 4 | 条件运算符 | 从右到左 | ... ? ... : … |
| 3 | 赋值 | 从右到左 | ... = … |
| … += … | |||
| … -= … | |||
| … **= … | |||
| … *= … | |||
| … /= … | |||
| … %= … | |||
| … <<= … | |||
| … >>= … | |||
| … >>>= … | |||
| … &= … | |||
| … ^= … | |||
| … |= … | |||
| … &&= … | |||
| … ||= … | |||
| … ??= … | |||
| 2 | yield | 从右到左 | yield … |
| yield* | yield* … | ||
| 1 | 展开运算符 | n/a | ... … |
| 0 | 逗号 | 从左到右 | … , … |
总结
那么说完了优先级,又说完结合性,还说完了运算顺序,那么我们再看开头的例子:
var a = 1;
b = a+++a
console.log(a, b)
js是怎么运行这个这行代码的
- 首先,js会先去计算b,得到第一步,也就是我们刚才说的运算顺序,b=是一个表达式
- 第二步,会去计算这个a++,得到结果是1,假设得到的结果为c,即 c的值为1,注意:这个时候a的值已经从1变为2了;
- 第三步,计算后面的这个a,得到2;
- 第四步,计算的就是c+2,即 1+2得到3,把这个结果值3传给b,这样就得到总的结果a=2,b=3。
那么大家可以看下面这个面试题,思考下这个结果会是什么呢?
var a = 1;
b = a++ + ++a
console.log(a, b)