一緒に創造し、成長するために一緒に働きましょう!「ナゲッツデイリー新プラン・8月アップデートチャレンジ」参加4日目ですイベント詳細はこちら
序文
今日コードを見ていたら、false && true || true
という形無意識のうちに短絡機能false &&
があると信じていたことがわかり、以下のステートメントは無視され、そのまま として扱わfalse
ただし、上記の式の結果はtrue
!
式の操作の順序を完全に把握していないことがわかったので、この記事を持っています
この記事では、演算子の優先順位に関する難しい問題を整理し、多くのプログラマーが式演算の順序を判断する際にエラーを起こしやすいポイントを説明し、最後にいくつかの質問をして、掘り下げた人がこの知識のポイントを完全に把握できるようにします。
要約表
早速、 MDNの優先度の要約表を最初に示しましょう。
優先順位 | オペレーターの種類 | 連想 | オペレーター |
---|---|---|---|
19 | グループ化 | 該当なし(関連なし) | ( … ) |
18 | 会員アクセス | 左から右へ | … . … |
計算されたメンバー アクセス | 左から右へ | … [ … ] |
|
new (パラメータリスト付き) |
なし | new … ( … ) |
|
関数呼び出し | 左から右へ | … ( … ) |
|
オプションの連鎖 | 左から右へ | ?. |
|
17 | new (パラメータリストなし) |
右から左へ | new … |
16 | ポストインクリメント | なし | … ++ |
後減分 | … -- |
||
15 | 論理否定 (!) | 右から左へ | ! … |
ビット単位の NOT (~) | ~ … |
||
単項加算 (+) | + … |
||
単項減算 (-) | - … |
||
プレインクリメント | ++ … |
||
プレデクリメント | -- … |
||
typeof |
typeof … |
||
void |
void … |
||
delete |
delete … |
||
await |
await … |
||
14 | パワー(**) | 右から左へ | … ** … |
13 | 乗算(*) | 左から右へ | … * … |
分割(/) | … / … |
||
残り (%) | … % … |
||
12 | 加算 (+) | 左から右へ | … + … |
減算 (-) | … - … |
||
11 | ビット単位の左シフト (<<) | 左から右へ | … << … |
ビット単位の右シフト (>>) | … >> … |
||
符号なし右シフト (>>>) | … >>> … |
||
10 | 未満 (<) | 左から右へ | … < … |
以下 (<=) | … <= … |
||
より大きい (>) | … > … |
||
以上 (>=) | … >= … |
||
in |
… in … |
||
instanceof |
… instanceof … |
||
9 | 等しい (==) | 左から右へ | … == … |
等しくない (!=) | … != … |
||
一貫性/厳密な平等 (===) | … === … |
||
一貫性がない/厳密に等しくない (!==) | … !== … |
||
8 | ビット論理積 (&) | 左から右へ | … & … |
7 | ビット単位の XOR (^) | 左から右へ | … ^ … |
6 | ビットごとの OR (|) | 左から右へ | … | … |
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
ましょう?
関数の長さの属性については、インタビューの失敗で詳細な紹介があります | 関数の長さ