“JavaScript⾥的很多奇技淫巧,都来⾃于对运算符的灵活使⽤。”
说到运算符的优先级,我们往往会想到一张见过无数次却从来没背下来的表。因为没背下来, 所以往往会认为它很简单,只要拿不准的时候去看看就好。我曾经也是这么认为的,直到在一个明媚的下午,我对着这张遍,遇到了几个问题。我才发现我其实并没有把它搞定。
希望你也能有一个阳光明媚的下午,来解开心中的这些困惑。
1 运算符基础
我们先看一下完整的
这张表说明了两个问题:
1.1 优先级: 优先级高的运算符最先被执行
问题: 1 || 1 ? 2 : 3 ; 答案:2 解析:||的优先级高 相当于: (1 || 1 )? 2 : 3 而不是: 1 || (1 ? 2 : 3 )复制代码
1.2 关联性: 运算符执行时的方向。是从左向右,还是从右向左
问题:+function (){var a = b = 1;}(); console.log(b); console.log(a); 答案:1 error 解析:赋值从右到左,var a = b = 1所以相当于 b = 1; var a = b; 那有同学可能会问,为什么不是? var b = 1; var a = b; 还记得变量提升吗?var a = b = 1;在变量提升的时候,只会把a去声明,并不会执行赋值中的b。 所以要想把b也声明了就需要按照语法 var a=1 , b ;复制代码
现在我们仔细把优先级的题改一下
1 || fn() && fn() 复制代码
MDN上写的是优先级高的运算符最先被执行,我们都知道 ||是短路的,后边不会执行。那么这个最先被执行的含义是什么呢?
1.3 短路:
- && 运算符的短路(a && b):如果a为假,b就不用执行了
- | | 运算符的短路(a || b):如果a为真,b就不用执行了
问题:1 || fn() && fn() 答案:1 fn不会执行 解析:就是利用&&运算符的短路原理啊。复制代码
讲到这有些同学会觉得很简单啊,就是这样啊,看到短路后边就不用算了啊。也有的同学可能会有点懵,不是说好了, 优先级高的先被执行吗?明明&&的优先级高啊。哈哈,别吵吵,我们一起看下一题。
问题:var a = 42; var b = "foo"; var c = 0; c || b ? a : b ; // 42 复制代码
刚才说短路的同学可能会说还是要参考优先级。刚才说优先级的同学可能一脸懵逼,静静地不想说话。那么我们开始今天的学习吧。
2 绑定
定义:运算符的优先级高先执行,并不是真正的执行,而是更强的绑定。
我们用上面来两个问题
1 || fn() && fn() // &&的优先级高,所以将后边的绑定 1 ||(fn() && fn()) // 所以相当于1 和(fn() && fn())的值去逻辑或 1 ||(fn() && fn()) // 我们查表,逻辑或从左到右执行。 1 ||(fn() && fn()) // 左执行,1是真值,所以短路,后边不执行复制代码
问题: var a = 42; var b = "foo"; var c = 0; c || b ? a : b ; 答案:42 解析:c || b ? a : b ; //查表 条件运算符权重是4,逻辑与符权重是6,所以逻辑与有更强的绑定 (c || b )? a : b ; //(c || b )相当于条件运算符里的条件 (c || b )? a : b ; //(c || b )值是0 ,所以值是 a 复制代码
好,我们在做两题巩固一下
问题: var a = 5; var b = 5; var c = 5+a+++b; [ a , c ] 答案: [6, 15] 解析: b = 5+a+++b; //查表 后置递增权重17 前置递增权重16 b = 5 +(a++)+ b; //++优先级更高,所以和绑定a绑定在一起 b = 5 +(a++)+ b; //根据语法后置递增先执行语句,后递增 b = 5 +(a++)+ b; //执行语句时a是5,所以b是15 b = 5 +(a++)+ b; //a在进行自增,得到6复制代码
问题: var a = 5; var b = 5; var c = ++a-b; [ a , c ] 答案: [6, 1] 解析: var c = ++a-b; //查表 前置递增权重和一元减权重都是16,从左往右执行 var c = ++a-b; //根据语法前置递增先递增,后执行语句 a = 6 var c = ++a-b; //执行语句时a是6,所以b是1复制代码
看到这,同学们可能恍然大悟,就这么回事啊。别急,我们来看下一题。 要解决这个问题,需要我们理解下一节的概念。
问题: var a = 42; var b = "foo"; var c = 0; a && b || c ? c || b ? a : c && b : a 复制代码
3 关联
定义:运算符的关联性去定义表达式的处理方向
来,用题说话
问题:a && b && c 的执行顺序 解析:(1)两个运算符都是&&,权重一样。所以这个时候就要看关联性。 (2)查表 &&的关联性是从左到右 (3)所以表达式应该是 ( a && b ) && c复制代码
问题:a ? b :c ? d : e 的执行顺序 解析:(1)两个运算符都是条件运算符,权重一样。所以这个时候就要看关联性。 (2)查表条件运算符的关联性是从右到左 (3)所以表达式应该是 a ? b :(c ? d : e )复制代码
好了,现在我们就可以轻松解决上面那个问题啦。
问题: var a = 42; var b = "foo"; var c = 0; a && b || c ? c || b ? a : c && b : a 答案: 42 解析:(a && b) || c ? c || b ? a :(c && b) : a //首先查表逻辑与权重是6最高 ((a && b) || c) ? c || b ? a :(c && b) : a //然后是逻辑或 ((a && b) || c) ? (c || b ? a :(c && b)) : a //两个条件运算符,权重一样。关联性从右到左复制代码
啊、、有没有很开心 最后的最后,我们来讲一个释疑
4 释疑
哈哈,释疑顾名思义就是解释调疑惑的地方,那最好的办法就是加()。
如果你能够熟练运用优先级/关联的规则,你的代码能更简洁,许多框架都是这样写的,非常漂亮。
但是你要叫不准,那就加()吧,千万别逞能,美其名曰有助于代码的可阅读性。
优先级 | 运算类型 | 关联性 | 运算符 |
---|---|---|---|
20 | n/a | ( … ) | |
19 | 从左到右 | … . … | |
从左到右 | … [ … ] | ||
n/a | new … ( … ) | ||
从左到右 | … ( … ) | ||
18 | 从右到左 | new … | |
17 | n/a | … ++ | |
… -- | |||
16 | 从右到左 | ! … | |
~ … | |||
+ … | |||
- … | |||
++ … | |||
-- … | |||
typeof … | |||
void … | |||
delete … | |||
await … | |||
15 | 从右到左 | … ** … | |
14 | 从左到右 | … * … | |
… / … | |||
… % … | |||
13 | 从左到右 | … + … | |
… - … | |||
12 | 从左到右 | … << … | |
… >> … | |||
… >>> … | |||
11 | 从左到右 | … < … | |
… <= … | |||
… > … | |||
… >= … | |||
… in … | |||
… instanceof … | |||
10 | 从左到右 | … == … | |
… != … | |||
… === … | |||
… !== … | |||
9 | 从左到右 | … & … | |
8 | 从左到右 | … ^ … | |
7 | 从左到右 | … | … | |
6 | 从左到右 | … && … | |
5 | 从左到右 | … || … | |
4 | 从右到左 | … ? … : … | |
3 | 从右到左 | … = … | |
… += … | |||
… -= … | |||
… *= … | |||
… /= … | |||
… %= … | |||
… <<= … | |||
… >>= … | |||
… >>>= … | |||
… &= … | |||
… ^= … | |||
… |= … | |||
2 | 从右到左 | yield … | |
yield* … | |||
1 | n/a | ... … | |
0 | 从左到右 | … , … |
参考: