keywords:operator precedence
,associativity
運算子優先序
在前一章我們介紹到運算式副作用, ()
本身無法定義一個新的包裹運算式,不過如果想要達到指定 99 到 b 可以運用
,
述句序列逗號運算子,這個運算子能將多個獨立的運算式述句串成單一個述句
ex1:
let a = 98,b;
b = (a++,a);
a; // 99
b; // 99
ex2:
let a = 98,b;
b = a++,a;
a; // 99
b; // 98
為何 b 會因為有無 () 值而有所不同? 因為 ,
運算子的優先序 ( precedence ) 比 =
運算子還要低,所以 ex2 會被解讀為這樣:
( b = a++ ), a;
而這在上一章有提到, a 會先指定給 b 也就是 98 ,然後 a 做 +1 的動作
故 a; // 99
b; // 98
先來看看 運算子優先序的完整列表
- 優先順序數字越高越優先 20 -> 1
當優先序相同時,使用相依性決定運算方向。範例如下:
a = b = 5; 左相依性 ( Left-associativity ) ,表示處理順序為從左至右 ( a OP b ) OP 5 右相依性 ( right-associativity ) 表示處理順序為從右至左 a OP ( b OP 5 ) 而賦值運算符 ( Assignment operators ) 為右相依性 a 和 b 的預期結果為 5,因為賦值運算符 ( Assignment operator ) 為右相依性,因此從右至左返回值 一開始 b 被設定為 5,接著 a 也被設定為 5。
&& 優先序比 || 還要高
let a = 98;
let b = 'xyz';
let c = false;
let d = a && b || c ? c || b ? a : c && b : a;
這什麼東西 😱 😱 😱
第一個部分先來看 a && b || c 到底會是什麼
false && true || true; // true
( false && true ) || true; // true
false && (true || true); // false
所以 && 運算子會被先估算,其次是 || 運算子
不過有可能是出自於左到右的處理順序嗎? 試著反轉運算子的順序
true || false && false; // true
( true || false ) && false; // false
true || (false && false); // true
結果是不會, && 還是會被先估算,所以證明是運算子優先序決定先後順序
短路 ( short circuiting )
- 對於 && 或 || 運算子來說,如果左手邊的運算元足以決定該運算的結果,那右手邊的運算元就不會被估算。因此產生短路 ( short circuited )
- 舉例來說, a && b 如果 a 是 falsy 的,b 就不會被估算,因為 && 運算的結果已然確立,沒有必要再檢查 b
a || b 中如果 a 是 truthy 的,此運算的結果就已經確定,沒理由再去檢查 b
function f1( opts ) if ( opts && opts.cool ){ .. } } 如果 opts 未設定 ( 又或者不是一個物件 ),運算式 opts.cool 就會擲出一個錯誤。 opts 的測試失敗加上短路,意味著 opts.cool 根本不會被估算,因此不會有錯誤產生! ---------------------------------------------- 同樣的 || 也可以有短路的行為: function f1( opts ){ if ( opts.cache || f2() ) { .. } } 先檢查 opts.cache 如果它存在就不會呼叫 f2() 函式,避免了可能白費的工夫
結合性 ( associativity )
根據運算子優先序的完整列表有寫道,如果優先序相同時,使用相依性決定運算方向
a && b && c
這樣的運算式中,歸組動作會隱含地發生,意味著 a && b 與 b && c 中會有一組先被估算
根據運算子優先序的完整列表
&& 及 || 是左結合
? : 及 = 是右結合
先前的例子:
let a = 98;
let b = 'xyz';
let c = false;
let d = a && b || c ? c || b ? a : c && b : a;
優先順序:
&& 為 6 從左至右
|| 為 5 從左至右
?: 為 4 從右至左
>> (a && b) || c ? c || b ? a : (c && b) : a;
>> ((a && b) || c) ? (c || b) ? a : (c && b) : a;
>> ((a && b) || c) ? ((c || b) ? a : (c && b)) : a;
---------------拆解開始---------------------------
1. ( 'xyz' || c ) ? ((c || b) ? a : false ) : a; 先計算 && 的運算式
2. 'xyz ? ( 'xyz' ? a : false ) : a; 再計算 || 的運算式
3. 'xyz' ? 98 : 98; 最後計算 ?: 的運算式
4. 98
結語:
- 我們應該在程式中搭配使用運算子優先序 / 結合性以及 () 手動歸組
- 如果能藉以寫出較短且較清楚的程式碼,就使用運算子優先序或結合性,在適合的地方使用 () 手動歸組來使程式碼更清楚明白,降低混淆