2022 年 11 月 14 日

基本運算子,數學

我們從學校學過許多運算子。它們像是加法 +、乘法 *、減法 - 等。

在本章中,我們將從簡單的運算子開始,然後專注於 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 ** ba 提升到 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 會被評估,然後賦值給左邊的變數:cba。最後,所有變數都共用一個值。

再次強調,為了可讀性的目的,最好將這樣的程式碼拆成幾行

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 是最常見的數值運算之一。

因此,有專門的運算子來處理它

  • 遞增 ++ 將變數增加 1

    let counter = 2;
    counter++;        // works the same as counter = counter + 1, but is shorter
    alert( counter ); // 3
  • 遞減 -- 將變數減少 1

    let 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 框架中都會使用此類技巧。這就是我們提到它們的原因。但通常它們不會改善程式碼的可讀性,因此我們在使用它們之前應該仔細思考。

任務

重要性:5

在以下程式碼之後,所有變數 abcd 的最終值是什麼?

let a = 1, b = 1;

let c = ++a; // ?
let d = b++; // ?

答案是

  • a = 2
  • b = 2
  • c = 2
  • d = 1
let a = 1, b = 1;

alert( ++a ); // 2, prefix form returns the new value
alert( b++ ); // 1, postfix form returns the old value

alert( a ); // 2, incremented once
alert( b ); // 2, incremented once
重要性:3

在以下程式碼之後,ax 的值是什麼?

let a = 2;

let x = 1 + (a *= 2);

答案是

  • a = 4(乘以 2)
  • x = 5(計算為 1 + 4)
重要性:5

這些表達式的結果是什麼?

"" + 1 + 0
"" - 1 + 0
true + false
6 / "3"
"2" * "3"
4 + 5 + "px"
"$" + 4 + 5
"4" - 2
"4px" - 2
"  -9  " + 5
"  -9  " - 5
null + 1
undefined + 1
" \t \n" - 2

仔細思考,寫下來,然後與答案進行比較。

"" + 1 + 0 = "10" // (1)
"" - 1 + 0 = -1 // (2)
true + false = 1
6 / "3" = 2
"2" * "3" = 6
4 + 5 + "px" = "9px"
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
"  -9  " + 5 = "  -9  5" // (3)
"  -9  " - 5 = -14 // (4)
null + 1 = 1 // (5)
undefined + 1 = NaN // (6)
" \t \n" - 2 = -2 // (7)
  1. 使用字串 "" + 1 加法會將 1 轉換為字串:"" + 1 = "1",然後我們有 "1" + 0,套用相同的規則。
  2. 減法 -(就像大多數數學運算一樣)只適用於數字,它會將空字串 "" 轉換為 0
  3. 使用字串進行加法會將數字 5 附加至字串。
  4. 減法總是會轉換為數字,因此它會將 " -9 " 轉換為數字 -9(忽略它周圍的空格)。
  5. null 在數字轉換後會變成 0
  6. undefined 在數字轉換後會變成 NaN
  7. 當字串轉換為數字時,會將字串開頭和結尾的空白字元修剪掉。這裡整個字串都由空白字元組成,例如 \t\n 以及它們之間的「一般」空白。因此,類似於空字串,它會變成 0
重要性:5

以下程式碼會要求使用者輸入兩個數字,並顯示它們的總和。

它的運作方式不正確。以下範例中的輸出為 12(預設提示值)。

為什麼?修正它。結果應該是 3

let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);

alert(a + b); // 12

原因是提示會將使用者輸入作為字串傳回。

因此變數分別具有值 "1""2"

let a = "1"; // prompt("First number?", 1);
let b = "2"; // prompt("Second number?", 2);

alert(a + b); // 12

我們應該在 + 之前將字串轉換為數字。例如,使用 Number() 或在前面加上 +

例如,在 prompt 之前

let a = +prompt("First number?", 1);
let b = +prompt("Second number?", 2);

alert(a + b); // 3

或是在 alert

let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);

alert(+a + +b); // 3

在最新的程式碼中同時使用一元和二元 +。看起來很奇怪,不是嗎?

教學課程地圖

留言

留言前請先閱讀以下內容…
  • 如果您有改進建議,請 提交 GitHub 問題 或發起拉取請求,而不是留言。
  • 如果您無法理解文章中的某些內容,請詳細說明。
  • 若要插入幾行程式碼,請使用 <code> 標籤,若要插入多行程式碼,請將它們包在 <pre> 標籤中,若要插入超過 10 行程式碼,請使用沙盒(plnkrjsbincodepen…)