我們從學校學過許多運算子。它們像是加法 +
、乘法 *
、減法 -
等。
在本章中,我們將從簡單的運算子開始,然後專注於 JavaScript 特有的面向,這些面向不在學校算術的範圍內。
術語:「一元」、「二元」、「運算元」
在我們繼續之前,讓我們了解一些常見的術語。
-
運算元 – 是運算子作用的對象。例如,在
5 * 2
的乘法中,有兩個運算元:左運算元是5
,右運算元是2
。有時,人們會稱這些為「參數」,而不是「運算元」。 -
如果運算子只有一個運算元,則該運算子為一元。例如,一元負號
-
會反轉數字的符號let x = 1; x = -x; alert( x ); // -1, unary negation was applied
-
如果運算子有兩個運算元,則該運算子為二元。同一個減號也存在二元形式
let x = 1, y = 3; alert( y - x ); // 2, binary minus subtracts values
正式來說,在上面的範例中,我們有兩個不同的運算子,它們共用同一個符號:負號運算子,一個反轉符號的一元運算子,以及減法運算子,一個從一個數字減去另一個數字的二元運算子。
數學
支援下列數學運算
- 加法
+
, - 減法
-
, - 乘法
*
, - 除法
/
, - 餘數
%
, - 指數
**
。
前四個很直接,而 %
和 **
需要說明一下。
餘數 %
餘數運算子 %
,儘管它的外觀,與百分比無關。
a % b
的結果是 a
除以 b
的整數除法的 餘數。
例如
alert( 5 % 2 ); // 1, the remainder of 5 divided by 2
alert( 8 % 3 ); // 2, the remainder of 8 divided by 3
alert( 8 % 4 ); // 0, the remainder of 8 divided by 4
指數 **
指數運算子 a ** b
將 a
提升到 b
的次方。
在學校數學中,我們將其寫為 ab。
例如
alert( 2 ** 2 ); // 2² = 4
alert( 2 ** 3 ); // 2³ = 8
alert( 2 ** 4 ); // 2⁴ = 16
就像在數學中一樣,指數運算子也定義為非整數。
例如,平方根是 ½ 的指數
alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root)
alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root)
使用二元 + 進行字串串接
讓我們認識一下 JavaScript 運算子超越學校算術的功能。
通常,加號運算子 +
會加總數字。
但是,如果二元 +
套用在字串上,它會合併(串接)它們
let s = "my" + "string";
alert(s); // mystring
請注意,如果任何運算元是字串,則另一個運算元也會轉換為字串。
例如
alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
請看,第一個運算元是字串或第二個運算元是字串並無所謂。
以下是一個更複雜的範例
alert(2 + 2 + '1' ); // "41" and not "221"
在這裡,運算子一個接一個運作。第一個 +
加總兩個數字,因此它傳回 4
,然後下一個 +
將字串 1
加到其中,因此就像 4 + '1' = '41'
。
alert('1' + 2 + 2); // "122" and not "14"
在這裡,第一個運算元是字串,編譯器也會將其他兩個運算元視為字串。2
會串接到 '1'
,因此就像 '1' + 2 = "12"
和 "12" + 2 = "122"
。
二元 +
是唯一以這種方式支援字串的運算子。其他算術運算子僅適用於數字,並始終將其運算元轉換為數字。
以下是減法和除法的範例
alert( 6 - '2' ); // 4, converts '2' to a number
alert( '6' / '2' ); // 3, converts both operands to numbers
數字轉換,一元 +
加號 +
有兩種形式:我們在上面使用的二元形式和一元形式。
一元加號或換句話說,加號運算子 +
套用於單一值,不會對數字做任何事。但是,如果運算元不是數字,一元加號會將其轉換為數字。
例如
// No effect on numbers
let x = 1;
alert( +x ); // 1
let y = -2;
alert( +y ); // -2
// Converts non-numbers
alert( +true ); // 1
alert( +"" ); // 0
它實際上與 Number(...)
做同樣的事,但較短。
將字串轉換為數字的需求非常頻繁。例如,如果我們從 HTML 表單欄位取得值,它們通常是字串。如果我們想加總它們怎麼辦?
二元加號會將它們當成字串加總
let apples = "2";
let oranges = "3";
alert( apples + oranges ); // "23", the binary plus concatenates strings
如果我們想將它們視為數字,我們需要先轉換再加總它們
let apples = "2";
let oranges = "3";
// both values converted to numbers before the binary plus
alert( +apples + +oranges ); // 5
// the longer variant
// alert( Number(apples) + Number(oranges) ); // 5
從數學家的角度來看,大量的正號可能看起來很奇怪。但從程式設計師的角度來看,這沒什麼特別的:一元正號會先套用,將字串轉換為數字,然後二元正號將它們加總。
為什麼一元正號會在二元正號之前套用在值上?正如我們將看到的,那是因為它們的優先順序較高。
運算子優先順序
如果一個表達式有多個運算子,執行順序會由它們的優先順序決定,或者換句話說,就是運算子的預設優先順序。
從學校我們都知道,在表達式 1 + 2 * 2
中的乘法應該在加法之前計算。這正是優先順序的運作方式。乘法被認為具有比加法更高的優先順序。
括號會覆寫任何優先順序,因此如果我們對預設順序不滿意,我們可以使用括號來改變它。例如,寫 (1 + 2) * 2
。
JavaScript 中有許多運算子。每個運算子都有對應的優先順序數字。數字較大的會先執行。如果優先順序相同,執行順序會從左到右。
以下是從優先順序表摘錄的內容(你不必記住這個,但請注意一元運算子的優先順序高於對應的二元運算子)
優先順序 | 名稱 | 符號 |
---|---|---|
… | … | … |
14 | 一元正號 | + |
14 | 一元負號 | - |
13 | 指數 | ** |
12 | 乘法 | * |
12 | 除法 | / |
11 | 加法 | + |
11 | 減法 | - |
… | … | … |
2 | 賦值 | = |
… | … | … |
正如我們所看到的,「一元正號」的優先順序為 14
,高於「加法」(二元正號)的 11
。這就是為什麼在表達式 "+apples + +oranges"
中,一元正號會在加法之前運作。
賦值
請注意,賦值 =
也是一個運算子。它在優先順序表中列為優先順序非常低的 2
。
這就是為什麼當我們賦值給變數時,例如 x = 2 * 2 + 1
,計算會先執行,然後再評估 =
,將結果儲存在 x
中。
let x = 2 * 2 + 1;
alert( x ); // 5
賦值 = 會傳回一個值
=
是運算子,而不是「神奇的」語言結構,這一點有一個有趣的含意。
JavaScript 中的所有運算子都會傳回一個值。對於 +
和 -
來說這是顯而易見的,但對於 =
也是一樣。
呼叫 x = value
會將 value
寫入 x
然後傳回它。
以下是一個示範,使用賦值作為更複雜表達式的一部分
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert( a ); // 3
alert( c ); // 0
在上面的範例中,表達式 (a = b + 1)
的結果是賦值給 a
的值(也就是 3
)。然後它會用於進一步的評估。
有趣的程式碼,不是嗎?我們應該了解它是如何運作的,因為有時我們會在 JavaScript 函式庫中看到它。
不過,請不要這樣寫程式碼。這樣的技巧絕對不會讓程式碼更清晰或更易於閱讀。
串連賦值
另一個有趣的特性是串連賦值的能力
let a, b, c;
a = b = c = 2 + 2;
alert( a ); // 4
alert( b ); // 4
alert( c ); // 4
串連賦值從右到左評估。首先,最右邊的表達式 2 + 2
會被評估,然後賦值給左邊的變數:c
、b
和 a
。最後,所有變數都共用一個值。
再次強調,為了可讀性的目的,最好將這樣的程式碼拆成幾行
c = 2 + 2;
b = c;
a = c;
這樣比較容易閱讀,特別是快速瀏覽程式碼時。
就地修改
我們經常需要對變數套用運算子,並將新的結果儲存在同一個變數中。
例如
let n = 2;
n = n + 5;
n = n * 2;
可以使用運算子 +=
和 *=
來縮短這個表示法
let n = 2;
n += 5; // now n = 7 (same as n = n + 5)
n *= 2; // now n = 14 (same as n = n * 2)
alert( n ); // 14
所有算術和位元運算子都存在簡短的「修改並賦值」運算子:/=
、-=
等。
這些運算子的優先權與一般的賦值相同,因此它們會在其他大多數運算之後執行
let n = 2;
n *= 3 + 5; // right part evaluated first, same as n *= 8
alert( n ); // 16
遞增/遞減
將數字增加或減少 1 是最常見的數值運算之一。
因此,有專門的運算子來處理它
-
遞增
++
將變數增加 1let counter = 2; counter++; // works the same as counter = counter + 1, but is shorter alert( counter ); // 3
-
遞減
--
將變數減少 1let counter = 2; counter--; // works the same as counter = counter - 1, but is shorter alert( counter ); // 1
遞增/遞減只能套用於變數。嘗試在值上使用它,例如 5++
,會產生錯誤。
運算子 ++
和 --
可以放在變數的前面或後面。
- 當運算子放在變數後面時,它處於「後置形式」:
counter++
。 - 「前置形式」是當運算子放在變數前面時:
++counter
。
這兩個陳述式都做同樣的事情:將 counter
增加 1
。
是否有差異?有,但我們只有在使用 ++/--
的回傳值時才能看到差異。
讓我們釐清一下。眾所周知,所有運算子都會回傳一個值。遞增/遞減也不例外。前置形式會回傳新的值,而後置形式會回傳舊的值(在遞增/遞減之前)。
以下是一個範例,用來顯示差異
let counter = 1;
let a = ++counter; // (*)
alert(a); // 2
在 (*)
行中,前置 形式 ++counter
會遞增 counter
並回傳新的值 2
。因此,alert
會顯示 2
。
現在,讓我們使用後置形式
let counter = 1;
let a = counter++; // (*) changed ++counter to counter++
alert(a); // 1
在 (*)
行中,後置 形式 counter++
也會遞增 counter
,但會回傳舊的值(在遞增之前)。因此,alert
會顯示 1
。
總結
-
如果不會使用遞增/遞減的結果,則使用哪種形式沒有差異
let counter = 0; counter++; ++counter; alert( counter ); // 2, the lines above did the same
-
如果我們想要增加一個值並立即使用運算子的結果,則需要使用前置形式
let counter = 0; alert( ++counter ); // 1
-
如果我們想要遞增一個值,但使用其前一個值,則需要使用後置形式
let counter = 0; alert( counter++ ); // 0
運算子 ++/--
也可以用在表達式中。它們的優先權高於大多數其他算術運算。
例如
let counter = 1;
alert( 2 * ++counter ); // 4
比較
let counter = 1;
alert( 2 * counter++ ); // 2, because counter++ returns the "old" value
雖然在技術上是可以的,但這種表示法通常會讓程式碼更難閱讀。一行會執行多件事,這不太好。
在閱讀程式碼時,快速的「垂直」眼睛掃描很容易遺漏像 counter++
這樣的東西,而且變數已增加這一點並不明顯。
我們建議採用「一行一個動作」的風格
let counter = 1;
alert( 2 * counter );
counter++;
位元運算子
位元運算子將參數視為 32 位元整數,並在二進位表示的層級上運作。
這些運算子並非 JavaScript 特有的。它們在大部分程式語言中都受支援。
運算子清單
- AND (
&
) - OR (
|
) - XOR (
^
) - NOT (
~
) - 左位移 (
<<
) - 右位移 (
>>
) - 零填充右位移 (
>>>
)
這些運算子很少使用,只有當我們需要在最低(位元)層級上調整數字時才會使用。我們很快就不會需要這些運算子,因為它們在網頁開發中很少使用,但在某些特殊領域(例如密碼學)中它們很有用。當有需要時,你可以閱讀 MDN 上的 位元運算子 章節。
逗號
逗號運算子 ,
是最罕見且最不尋常的運算子之一。有時,它用於撰寫較短的程式碼,因此我們需要知道它才能了解正在發生的事情。
逗號運算子允許我們評估多個表達式,並用逗號 ,
將它們分開。每個表達式都會被評估,但只會傳回最後一個表達式的結果。
例如
let a = (1 + 2, 3 + 4);
alert( a ); // 7 (the result of 3 + 4)
在此,第一個表達式 1 + 2
會被評估,而其結果會被捨棄。然後,3 + 4
會被評估並傳回作為結果。
請注意,逗號運算子的優先順序非常低,低於 =
,因此在上述範例中,括號非常重要。
如果不使用括號:a = 1 + 2, 3 + 4
會先評估 +
,將數字加總為 a = 3, 7
,然後賦值運算子 =
會將 a = 3
賦值,而其餘部分會被忽略。這就像 (a = 1 + 2), 3 + 4
。
為什麼我們需要一個運算子,它會捨棄最後一個表達式以外的所有內容?
有時,人們會在更複雜的結構中使用它,以便在一行中放置多個動作。
例如
// three operations in one line
for (a = 1, b = 3, c = a * b; a < 10; a++) {
...
}
許多 JavaScript 框架中都會使用此類技巧。這就是我們提到它們的原因。但通常它們不會改善程式碼的可讀性,因此我們在使用它們之前應該仔細思考。
留言
<code>
標籤,若要插入多行程式碼,請將它們包在<pre>
標籤中,若要插入超過 10 行程式碼,請使用沙盒(plnkr、jsbin、codepen…)