理解JavaScript中的运算符优先级规则
运算符优先级
写在最前
js 代码如下,请问在控制台中输出的结果为?
var a = "hello"
console.log("This is " + (a== "hello") ? 'Mine' : 'Yours')
如果先入为主求结果的话,我们很容易就想到输出结果为:This is Mine
。当我们这么想时,我们其实就已经掉入了出题者设计的大坑中了~该题考察的是运算符优先级,我们细分此题计算过程:
Step1:括号改变优先级,因此先算括号里面的值,得到结果:"This is ” + true ? 'Mine' : ‘Yours’
;
Step2:+
的优先级要高于 ?:
,因此先算 + 号,得到结果:"This is true" ? 'Mine' : ‘Yours’
;
Step3:计算? :
由于前面的字符串不为空,转为 bool 值为true
,因此,整个表达式的结果为 Mine
;
综上所述,此题的运行结果为 Mine
。
JavaScript运算符优先级
JavaScript运算符均有明确的优先级与结合性。优先级较高的运算符将先于优先级较低的运算符进行运算。结合性是指具有同等优先级的运算符将按照怎样的顺序进行运算。结合性有向左结合和向右结合两种。
例如,表达式a+b+c
,向左结合就是先计算a+b
,即(a+b)+c
;而向右结合就是先计算b+c
,即a+(b+c)
。
运算符优先级从高到低:
- 圆括号运算符:
()
,优先级最高 - 一元运算符:
++
、--
、!
- 算数运算符:先
*
、/
、%
,后+
、-
- 关系运算符:
<
、<=
、>
、>=
- 相等运算符:
==
、!=
、===
、!==
- 逻辑运算符:先
&&
,后||
- 三目运算符:
?:
- 赋值运算符:
=
- 逗号运算符:
,
圆一算关、相逻三赋逗!
下表按从最高到最低的优先级列出JavaScript运算符,具有相同优先级的运算符按从左至右的顺序求值。
优先级 | 分类 | 运算符 | 结合性 |
---|---|---|---|
1 | 后缀运算符 | ( )、[ ]、-> | 从左到右 |
2 | 单目运算符 | !、*(指针)、& 、++、--、+(正号)、-(负号) | 从右到左 |
3 | 乘法/除法/取余 | *(乘号)、/、% | 从左到右 |
4 | 加法/减法 | +、- | 从左到右 |
5 | 位移运算符 | <<、>> | 从左到右 |
6 | 关系运算符 | <、<=、>、>= | 从左到右 |
7 | 相等/不等 | ==、!= | 从左到右 |
8 | 按位与 | & | 从左到右 |
9 | 按位异或 | ^ | 从左到右 |
10 | 按位或 | | | 从左到右 |
11 | 逻辑与 | && | 从左到右 |
12 | 逻辑或 | || | 从左到右 |
13 | 三目运算符 | ? : | 从右到左 |
14 | 赋值运算符 | =、+=、-=、*=、/=、 %=、 >>=、 <<=、&=、^=、|= | 从右到左 |
15 | 逗号运算符 | , | 从左到右 |
小试牛刀
-
案例1(小括号 与 算术运算符)
var a = 1 + 2 * 3 //7 var b = 10 - 2 / 4 //9.5 console.log(`a = ${a}, b = ${b}`) //a = 7, b = 9.5 var a = (1 + 2) * 3 //9 var b = (10 - 2) / 4 //2 console.log(`a = ${a}, b = ${b}`) //a = 9, b = 2
括号运算符
>算数运算符
,并且结合性均为从左到右 -
案例2(小括号 与 逻辑运算符、关系运算符 与 逻辑运算符)
a || b * c // 首先对 `a` 求值,如果 `a` 为真值则直接返回 `a` a && b < c // 首先对 `a` 求值,如果 `a` 为虚值则直接返回 `a` a ?? (b || c) // 首先对 `a` 求值,如果 `a` 不是 `null` 或 `undefined` 则直接返回 `a` //a?.b.c // 首先对 `a` 求值,如果 `a` 是 `null` 或 `undefined` 则直接返回 `undefined` 3 > 2 && 2 > 1 // 返回 true,此处相当于true && true 3 > 2 > 1 // 返回 false,因为 3 > 2 是 true,然后 true 会在比较运算符中被隐式转换为 1 // 因此 true > 1 会变为 1 > 1,结果是 false // 加括号可以更清楚:(3 > 2) > 1
括号运算符
>算数运算符
>逻辑运算符
,并且结合性为从左到右 -
案例3(关系运算符、相等运算符、三目运算符混用)
let x = 1 let arr = [] let y = arr.length <= 0 || arr[0] === undefined ? x : arr[0] console.log(y) //1 //1.先计算最左边arr.length <= 0,为true //2.再计算arr[0] === undefined,为true //3.再计算true||true?x:arr[0]的最左边 //4.最后计算true?x:arr[0] //5.最终结果为y = x,即y = 1
关系运算符
>相等运算符
>三目运算符
,并且结合性为从左到右 -
案例4(结合性)
w = x = y = z //w = (x = (y = z)) q = a ? b : c ? d : e ? f : g //q = a ? b : (c ? d : (e ? f : g));
三目运算符
>赋值运算符
,并且结合性为从右到左 -
案例5(逻辑运算符)
- 短路运算,即运算符两端的两个逻辑表达式,当第一个逻辑表达式不符合条件时,就会得出结果,不会再去计算第二个逻辑表达式
- 当逻辑表达式不是布尔值的结果时,会进行隐式转换为布尔值,但是最后运算结果,返回的不是布尔值,而是进行隐式转换之前的原结果(大坑)
console.log(1 || 2) //1,此处为或运算,左边为true,逻辑中断,不用计算右边,直接输出或运算符左边的原结果,即1 console.log(1 || 0) //1,此处为或运算,左边为true,逻辑中断,不用计算右边,直接输出或运算符左边的原结果,即1 console.log(0 || 1) //1,此处为或运算,左边为false,右边计算为true,输出或运算符右边的原结果,即1 console.log(0 || 0) //0,此处为或运算,左边为false,右边计算为false,输出或运算符右边的原结果,即0 console.log(1 && 2) //2,此处为与运算,左边为true,右边计算为true,输出与运算符右边的原结果,即2 console.log(1 && 0) //0,此处为与运算,左边为true,右边计算为false,输出与运算符右边的原结果,即0 console.log(0 && 1) //0,此处为与运算,左边为false,逻辑中断,不用计算右边,直接输出与运算符左边的原结果,即0 //实际例子举例 let age = 10; console.log(true && age++); //10,++在右边,不参与运算 console.log(true && ++age); //12,++在左边,参与运算 console.log(age); //12 console.log(true || false && false) // true //这是一个很经典的例子,如果我们不知道 && 的优先级比 || 高,我们肯定以为返回值是false
逻辑与
>逻辑或
,并且结合性为从左到右 -
案例6(三目运算符 与 逻辑与)
console.log(1 && 0 ? 3 : 2) // 2 //&&的运算级比三目运算符更高,先计算&&
逻辑与
>三目运算符
,且逻辑与结合性为从左到右,三目运算符结合性为从右到左 -
案例7(typeof 与 三目运算符)
let flag = false console.log(typeof flag) // boolean console.log(typeof flag ? '2' : 1) // 2,此处?前面的typeof flag值为boolean,为一个字符串,隐式转换为true
typeof
>三目运算符
,typeof结合性为从左到右,三目运算符结合性为从右到左 -
案例8(三目运算符 与 相等运算符==)
let value = "3" let index = 3 let flag = value == index ? 1 : 2 //1.先算value == index,结果为true //2.再计算 true ? 1 : 2,结果为1 //3.最后赋值给flag,即flag=1 console.log(flag) // 1
相等运算符==
>三目运算符
>赋值运算符
,赋值运算符与相等运算符结合性为从左到右,三目运算符结合性为从右到左
总结
运算符优先级小结:
后缀运算符
>一元运算符
>算数运算符
>关系运算符
>相等运算符
>逻辑运算符
>三目运算符
>赋值运算符
>逗号运算符
附录
运算符优先级一览表:
优先级 | 运算符 | 描述 | 例子 |
---|---|---|---|
18 | ( ) | 表达式分组 | (100 + 50) * 3 |
17 | . | 属于成员 | person.name |
17 | [] | 属于成员 | person["name"] |
17 | ?. | 可选链 ES2020 | x ?. y |
17 | () | 函数调用 | myFunction() |
17 | new | 带参的新建 | new Date("June 5,2022") |
16 | new | 不带参的新建 | new Date() |
增量运算符后缀递增在前缀递增之前执行。 | |||
15 | ++ | 后缀递增 | i++ |
15 | -- | 后缀递减 | i-- |
14 | ++ | 前缀递增 | ++i |
14 | -- | 前缀递减 | --i |
NOT 运算符 | |||
14 | ! | 逻辑非 | !(x==y) |
14 | ~ | 按位非 | ~x |
一元运算符 | |||
14 | + | 一元加 | +x |
14 | - | 一元减 | -x |
14 | typeof | 数据类型 | typeof x |
14 | void | 评估空 | void(0) |
14 | delete | 属性删除 | delete myCar.color |
算术运算符指数运算在乘法之前执行。乘法和除法在加法和减法之前执行。 | |||
13 | ** | 指数运算 ES2016 | 10 ** 2 |
12 | * | 乘 | 10 * 5 |
12 | / | 除 | 10 / 5 |
12 | % | 除法余数 | 10 % 5 |
11 | + | 加 | 10 + 5 |
11 | - | 减 | 10 - 5 |
11 | + | 串联 | "Bill" + "Gates" |
移位运算符 | |||
10 | << | 左移 | x << 2 |
10 | >> | 右移(带符号) | x >> 2 |
10 | >>> | 右移(无符号) | x >>> 2 |
关系运算符 | |||
9 | in | 对象中的属性 | "PI" in Math |
9 | instanceof | 对象的实例 | x instanceof Array |
比较运算符 | |||
9 | < | 小于 | x < y |
9 | <= | 小于或等于 | x <= y |
9 | 大于 | x > y | |
9 | >= | 大于或等于 | x >= Array |
8 | == | 等于 | x == y |
8 | === | 严格相等 | x === y |
8 | != | 不相等 | x != y |
8 | !== | 严格不相等 | x !== y |
位运算符 | |||
7 | & | 按位与 | x & y |
6 | 按位异或 | x ^ y | |
5 | | | 按位或 | x | y |
逻辑运算符 | |||
4 | && | 逻辑与 | x && y |
3 | || | 逻辑或 | x || y |
3 | ?? | 空值合并 ES2020 | x ?? y |
条件(三元)运算符 | |||
2 | ? : | 条件 | ? "yes" : "no" |
赋值运算符赋值在其他操作之后执行。 | |||
2 | = | 简单赋值 | x = y |
2 | : | 冒号赋值 | x: 5 |
2 | += | 加法赋值 | x += y |
2 | -= | 减法赋值 | x -= y |
2 | *= | 乘法赋值 | x *= y |
2 | **= | 指数赋值 | x **= y |
2 | /= | 除法赋值 | x /= y |
2 | %= | 取余赋值 | x %= y |
2 | <<= | 左移赋值 | x <<= y |
2 | >>= | 右移赋值 | x >>= y |
2 | >>>= | 无符号右移 | x >>>= y |
2 | &= | 位与赋值 | x &= y |
2 | |= | 位或赋值 | x |= y |
2 | ^= | 位异或赋值 | x ^= y |
2 | &&= | 逻辑与赋值 | x &= y |
2 | ||= | 逻辑或赋值 | x ||= y |
2 | => | 箭头 | x => y |
2 | yield | 暂停 / 恢复 | yield x |
2 | yield* | 委托运算符 | yield* x |
2 | ... | 展开运算符 | ... x |
1 | , | 逗号 | x , y |
注:本文举例均来自于互联网
优先级官方定义文档跳转:w3school、MDN
推荐阅读
-
深入理解 .NET Core 中的 Linq 查询运算符(第二部分)
-
理解JavaScript中style.cssText的使用 - 起步篇:深入探究cssText的本质
-
理解JavaScript中的CSSText属性:优点与兼容性与element.style的对比
-
理解CentOS中物理和虚拟网卡的命名规则与信息获取方法
-
理解JavaScript中的求模和取余操作:为什么摸的值会和x的符号相同?
-
理解 Python 中的取模运算符
-
理解 JavaScript 中的逻辑运算符:|| 和 && 的用法
-
理解JavaScript中的事件委派与代理 - 用代码塑造世界 - OSCHINA 开源技术社区
-
理解JavaScript中的事件委派或代理机制
-
理解JavaScript中的事件代理机制