你知道false&&true||true结果吗 | 聊聊运算符优先级
前言
今天看代码的时候,遇到一个 false && true || true
形式的判断条件,看到了 false &&
下意识地认为存在短路特性,后面语句的就不管了,直接当成 false
处理
然而,上面这个表达式的结果却是 true
!
发现自己对表达式运算顺序并没有完全掌握,所以就有了这篇文章
本文梳理了关于运算符优先级的疑难问题,解释了许多程序员在判断表达式运算顺序的易错点,并在末尾出了几道题,希望能帮助掘友们完全掌握这一知识点。
汇总表
话不多说,先展示一下 MDN 的优先级汇总表
优先级 | 运算符类型 | 结合性 | 运算符 |
---|---|---|---|
19 | 分组 | n/a(不相关) | ( … ) |
18 | 成员访问 | 从左到右 | … . … |
需计算的成员访问 | 从左到右 | … [ … ] |
|
new
(带参数列表)
|
n/a | new … ( … ) |
|
函数调用 | 从左到右 |
… (
…
)
|
|
可选链(Optional chaining) | 从左到右 | ?. |
|
17 |
new
(无参数列表)
|
从右到左 | new … |
16 | 后置递增 | n/a | … ++ |
后置递减 | … -- |
||
15 | 逻辑非 (!) | 从右到左 | ! … |
按位非 (~) | ~ … |
||
一元加法 (+) | + … |
||
一元减法 (-) | - … |
||
前置递增 | ++ … |
||
前置递减 | -- … |
||
typeof
|
typeof … |
||
void
|
void … |
||
delete
|
delete … |
||
await
|
await … |
||
14 | 幂 (**) | 从右到左 | … ** … |
13 | 乘法 (*) | 从左到右 | … * … |
除法 (/) | … / … |
||
取余 (%) | … % … |
||
12 | 加法 (+) | 从左到右 | … + … |
减法 (-) | … - … |
||
11 | 按位左移 (<<) | 从左到右 | … << … |
按位右移 (>>) | … >> … |
||
无符号右移 (>>>) | … >>> … |
||
10 | 小于 (<) | 从左到右 | … < … |
小于等于 (<=) | … <= … |
||
大于 (>) | … > … |
||
大于等于 (>=) | … >= … |
||
in
|
… in … |
||
instanceof
|
… instanceof … |
||
9 | 相等 (==) | 从左到右 | … == … |
不相等 (!=) | … != … |
||
一致/严格相等 (===) | … === … |
||
不一致/严格不相等 (!==) | … !== … |
||
8 | 按位与 (&) | 从左到右 | … & … |
7 | 按位异或 (^) | 从左到右 | … ^ … |
6 | 按位或 (|) | 从左到右 | … | … |
5 | 逻辑与 (&&) | 从左到右 | … && … |
4 | 逻辑或 (||) | 从左到右 | … || … |
空值合并 (??) | 从左到右 | … ?? … |
|
3 | 条件(三元)运算符 | 从右到左 | … ? … : … |
2 | 赋值 | 从右到左 | … = … |
… += … |
|||
… -= … |
|||
… **= … |
|||
… *= … |
|||
… /= … |
|||
… %= … |
|||
… <<= … |
|||
… >>= … |
|||
… >>>= … |
|||
… &= … |
|||
… ^= … |
|||
… |= … |
|||
… &&= … |
|||
… ||= … |
|||
… ??= … |
|||
1 | 逗号 / 序列 | 从左到右 | … , … |
疑难点
优先级
所谓优先级,就是同样一个表达式,先算那个,后算那个
最简单的例子就是加减乘除,先乘除后加减,对于这个表达式 2 + 2 * 2
,相当于 2 + (2 * 2)
,结果为 6
我们只要在计算表达式时,按照优先级给表达式加括号,运算顺序就清晰了
由于逻辑与比逻辑或的优先级高,false && true || true
等同于 (false && true) || true
,这样看起来是不是一眼就能出结果了
结合性
结合性只针对二元表达式,决定连续的同级运算符之间,要如何结合。
不太好理解,好在正儿八经的右结合性的只有幂
举个例子大家就明白了
2 * 2 * 3
等同于(2 * 2) * 3
结果为12
- 而
2 ** 2 ** 3
等同于2 ** (2 ** 3)
结果为256
new
和条件运算符并不是二元的,结合性对他们是不生效的
另一个右结合性的二元运算符是赋值,我们基本不会连续使用,因为它要求左值为变量,但还是举个例子讲一下吧,看下面这段代码:
let a = 1
a += a *= a -= 1
运算流程是这样的
- 根据右结合原理,上面的表达式化为
a += (a *= (a -= 1))
- 展开等号,
a = a + (a = a * (a = a - 1))
- 忽略多余的赋值运算,变量赋值,
a = 1 + (1 * (1 - 1))
- 最后表达式结果为
1
,a
的值也还是1
这里牵扯到 JS 的另一个特性:在一个表达式中,多余的赋值运算会被忽略。上面那串代码,扔到 C++ 中执行结果是
0
。对此不必深究,我们在开发中是绝对用不上的
逻辑短路
拥有逻辑短路的有三个运算符:逻辑与、逻辑或、条件运算符
let num = 1
const add = () => num++
false && add()
true || add()
false ? add() : 0
true ? 0 : add()
console.log(num) // 1
在上面这个例子中,按理来说函数调用的优先级远高于逻辑/条件运算符,但是由于逻辑短路, add
一次都没有执行。
逻辑短路没有原因,是一种语言特性,你可以将其理解为懒求值:只有真正用到的时候,才进行计算表达式的值。
而上面的几句表达式的结果在函数执行前就已经确定了,函数也就没有必要执行了。
前置/后置运算符
一句话说明白,以递增运算符举例,前置递增先加后运算,后置递增先运算再加
let a=1, b=1
console.log(a++) // 1
console.log(++b) // 2
逗号运算符
逗号作为运算符时,其优先级是最低的。
有逗号分割的表达式,不管前面的结果再花哨。都以最后一段表达式的结果返回
for (let i = 0; true, false; i++) {
console.log(i) // 一次也不会执行
}
注意逗号作为运算符和操作符的区别
console.log(1, 2, 3, 4, 5, 6) // 1 2 3 4 5 6
console.log((1, 2, 3, 4, 5, 6)) // 6
console.log([1, 2, 3]) // [1, 2, 3]
console.log([(1, 2, 3)]) // [3]
逗号只有在表达式中作运算符使用,在其他地方都是作为操作符使用的,如:函数传参,变量声明、数组/对象定义
扩展运算符
有些掘友应该已经发现了,上面的优先级汇总表中漏了扩展运算符...
,原因在于虽然我们平时叫它扩展运算符,但它其实是一种语法,只能在函数/数组/对象中使用,得到的结果是一种特殊的迭代器
我对其进行了测试,发现它的优先级低于所有运算符
let a = '123'
console.log(...a && '321') // 3 2 1
console.log(...a = '321') // 3 2 1
console.log(...a, '321') // 1 2 3 321
最后一条语句逗号是作为操作符使用的,由于 ...
使用场景的限制,无法与逗号运算符进行比较
练习题
出几道练习题,掘友们可以尝试做一下
问题
// 第一题
let a = 1
console.log(a-- + --a)
console.log(a)
// 第二题
console.log(!2 - 1 || 0 - 2)
// 第三题
console.log(0 < 1 < 2 == 2 > 1 > 0)
// 第四题
console.log(...typeof 1)
// 第五题
var top = 1['toString'].length
var fun = new new Function()()
console.log((1 > 2 ? 1 : 2 - 10 ? 4 : 2, 1 ? top : fun))
答案
第一题:
- 递减运算优先于加法运算,加括号:
(a--) + (--a)
- 后置递减先运算再减,所以加号左边为
1
,此时a
为0
- 前置加法先减后运算,所以加号右边为
-1
,此时a
为-1
- 运算结果为
1 + -1 = 0
,a
最终为-1
第二题:
- 逻辑非优先于减法优先于逻辑或,加括号:
((!2) - 1) || (0 - 2)
!2
转化为布尔为false
,再转化为数字为0
,式子为:(0 - 1) || (0 - 2)
- 逻辑或前一项为
-1
,为真值,触发逻辑短路,运算结果为-1
第三题:
- 小于/大于优先于等于,加括号:
((0 < 1) < 2) == ((2 > 1) > 0)
- 左边
true < 2
,true
转化为数字1 < 2
,左边结果为true
- 右边
true > 0
,true
转化为数字1 > 0
,右边结果也为true
- 所以,运算结果为
true
第四题:
typeof 1
结果为'number'
- 展开输出结果为
n u m b e r
第五题:
- 注意表达式中间有个逗号,所以前半部分不用管,只看后半部分
1 ? top : fun
,1
为真值,所以结果为Window
解题过程中进行了多次类型转化,对这方面不熟悉的可以看细述 JS 各数据类型的检测与转换
不理解最后一题为什么是 Window
的,来看看top === window ? 因为使用 var 遭遇的坑吧
关于函数的 length 属性,在面试败笔 | 函数的length有详细的介绍
转载自:https://juejin.cn/post/7134250195641958430