你知道false&&true||true结果吗 | 聊聊运算符优先级

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 4 天,点击查看活动详情

前言

今天看代码的时候,遇到一个 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
复制代码

运算流程是这样的

  1. 根据右结合原理,上面的表达式化为 a += (a *= (a -= 1))
  2. 展开等号, a = a + (a = a * (a = a - 1))
  3. 忽略多余的赋值运算,变量赋值, a = 1 + (1 * (1 - 1))
  4. 最后表达式结果为 1a 的值也还是 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,此时 a0
  • 前置加法先减后运算,所以加号右边为 -1,此时 a-1
  • 运算结果为 1 + -1 = 0a 最终为 -1

第二题:

  • 逻辑非优先于减法优先于逻辑或,加括号:((!2) - 1) || (0 - 2)
  • !2 转化为布尔为 false,再转化为数字为 0,式子为:(0 - 1) || (0 - 2)
  • 逻辑或前一项为 -1,为真值,触发逻辑短路,运算结果为 -1

第三题:

  • 小于/大于优先于等于,加括号:((0 < 1) < 2) == ((2 > 1) > 0)
  • 左边 true < 2true 转化为数字 1 < 2,左边结果为 true
  • 右边 true > 0true 转化为数字 1 > 0,右边结果也为 true
  • 所以,运算结果为 true

第四题:

  • typeof 1 结果为 'number'
  • 展开输出结果为 n u m b e r

第五题:

  • 注意表达式中间有个逗号,所以前半部分不用管,只看后半部分 1 ? top : fun1 为真值,所以结果为 Window

解题过程中进行了多次类型转化,对这方面不熟悉的可以看细述 JS 各数据类型的检测与转换

不理解最后一题为什么是 Window的,来看看top === window ? 因为使用 var 遭遇的坑

关于函数的 length 属性,在面试败笔 | 函数的length有详细的介绍

猜你喜欢

转载自juejin.im/post/7134250195641958430