“JavaScript⾥的很多奇技淫巧,都來⾃於對運算子的靈活使⽤。”
說到運算子的優先順序,我們往往會想到一張見過無數次卻從來沒背下來的表。因為沒背下來, 所以往往會認為它很簡單,只要拿不準的時候去看看就好。我曾經也是這麼認為的,直到在一個明媚的下午,我對著這張遍,遇到了幾個問題。我才發現我其實並沒有把它搞定。
《如晴天,似雨天》希望你也能有一個陽光明媚的下午,來解開心中的這些困惑。
1 運算子基礎
我們先看一下完整的JavaScript運算子優先順序表(文章最後,移動端錨點連結不好用,手滑下去吧)
這張表說明了兩個問題:
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 | 成員訪問 | 從左到右 | … . … |
需計算的成員訪問 | 從左到右 | … [ … ] | |
new (帶引數列表) | n/a | new … ( … ) | |
函式呼叫 | 從左到右 | … ( … ) | |
18 | new (無引數列表) | 從右到左 | new … |
17 | 後置遞增(運算子在後) | n/a | … ++ |
後置遞減(運算子在後) | … -- | ||
16 | 邏輯非 | 從右到左 | ! … |
按位非 | ~ … | ||
一元加法 | + … | ||
一元減法 | - … | ||
前置遞增 | ++ … | ||
前置遞減 | -- … | ||
typeof | typeof … | ||
void | void … | ||
delete | delete … | ||
await | await … | ||
15 | 冪 | 從右到左 | … ** … |
14 | 乘法 | 從左到右 | … * … |
除法 | … / … | ||
取模 | … % … | ||
13 | 加法 | 從左到右 | … + … |
減法 | … - … | ||
12 | 按位左移 | 從左到右 | … << … |
按位右移 | … >> … | ||
無符號右移 | … >>> … | ||
11 | 小於 | 從左到右 | … < … |
小於等於 | … <= … | ||
大於 | … > … | ||
大於等於 | … >= … | ||
in | … in … | ||
instanceof | … instanceof … | ||
10 | 等號 | 從左到右 | … == … |
非等號 | … != … | ||
全等號 | … === … | ||
非全等號 | … !== … | ||
9 | 按位與 | 從左到右 | … & … |
8 | 按位異或 | 從左到右 | … ^ … |
7 | 按位或 | 從左到右 | … | … |
6 | 邏輯與 | 從左到右 | … && … |
5 | 邏輯或 | 從左到右 | … || … |
4 | 條件運算子 | 從右到左 | … ? … : … |
3 | 賦值 | 從右到左 | … = … |
… += … | |||
… -= … | |||
… *= … | |||
… /= … | |||
… %= … | |||
… <<= … | |||
… >>= … | |||
… >>>= … | |||
… &= … | |||
… ^= … | |||
… |= … | |||
2 | yield | 從右到左 | yield … |
yield* | yield* … | ||
1 | 展開運算子 | n/a | ... … |
0 | 逗號 | 從左到右 | … , … |
參考: