我們從學校裏了解到過很多運算符,比如說加號 +
、乘號 *
、減號 -
等。
在本章中,我們將從簡單的運算符開始,然後著重介紹 JavaScript 特有的方面,這些是在學校中學習的數學運算所沒有涵蓋的。
在正式開始前,我們先簡單浏覽壹下常用術語。
運算元 —— 運算符應用的對象。比如說乘法運算 5 * 2
,有兩個運算元:左運算元 5
和右運算元 2
。有時候人們也稱其爲“參數”而不是“運算元”。
如果壹個運算符對應的只有壹個運算元,那麽它是 壹元運算符。比如說壹元負號運算符(unary negation)-
,它的作用是對數字進行正負轉換:
let x = 1; x = -x; alert( x ); // -1,壹元負號運算符生效
如果壹個運算符擁有兩個運算元,那麽它是 二元運算符。減號還存在二元運算符形式:
let x = 1, y = 3; alert( y - x ); // 2,二元運算符減號做減運算
嚴格地說,在上面的示例中,我們使用壹個相同的符號表征了兩個不同的運算符:負號運算符,即反轉符號的壹元運算符,減法運算符,是從另壹個數減去壹個數的二元運算符。
支持以下數學運算:
加法 +
,
減法 -
,
乘法 *
,
除法 /
,
取余 %
,
求冪 **
.
前四個都很簡單,而 %
和 **
則需要說壹說。
取余運算符是 %
,盡管它看起來很像百分數,但實際並無關聯。
a % b
的結果是 a
整除 b
的 余數。
例如:
alert( 5 % 2 ); // 1,5 除以 2 的余數 alert( 8 % 3 ); // 2,8 除以 3 的余數
求冪運算 a ** b
將 a
提升至 a
的 b
次冪。
在數學運算中我們將其表示爲 ab。
例如:
alert( 2 ** 2 ); // 2² = 4 alert( 2 ** 3 ); // 2³ = 8 alert( 2 ** 4 ); // 2⁴ = 16
就像在數學運算中壹樣,冪運算也適用于非整數。
例如,平方根是指數爲 ½ 的冪運算:
alert( 4 ** (1/2) ); // 2(1/2 次方與平方根相同) alert( 8 ** (1/3) ); // 2(1/3 次方與立方根相同)
我們來看壹些學校算術未涉及的 JavaScript 運算符的特性。
通常,加號 +
用于求和。
但是如果加號 +
被應用于字符串,它將合並(連接)各個字符串:
let s = "my" + "string"; alert(s); // mystring
注意:只要任意壹個運算元是字符串,那麽另壹個運算元也將被轉化爲字符串。
舉個例子:
alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21"
妳看,第壹個運算元和第二個運算元,哪個是字符串並不重要。
下面是壹個更複雜的例子:
alert(2 + 2 + '1' ); // "41",不是 "221"
在這裏,運算符是按順序工作。第壹個 +
將兩個數字相加,所以返回 4
,然後下壹個 +
將字符串 1
加入其中,所以就是 4 + '1' = '41'
。
alert('1' + 2 + 2); // "122",不是 "14"
這裏,第壹個操作數是壹個字符串,所以編譯器將其他兩個操作數也視爲了字符串。2
被與 '1'
連接到了壹起,也就是像 '1' + 2 = "12"
然後 "12" + 2 = "122"
這樣。
二元 +
是唯壹壹個以這種方式支持字符串的運算符。其他算術運算符只對數字起作用,並且總是將其運算元轉換爲數字。
下面是減法和除法運算的示例:
alert( 6 - '2' ); // 4,將 '2' 轉換爲數字 alert( '6' / '2' ); // 3,將兩個運算元都轉換爲數字
加號 +
有兩種形式。壹種是上面我們剛剛討論的二元運算符,還有壹種是壹元運算符。
壹元運算符加號,或者說,加號 +
應用于單個值,對數字沒有任何作用。但是如果運算元不是數字,加號 +
則會將其轉化爲數字。
例如:
// 對數字無效 let x = 1; alert( +x ); // 1 let y = -2; alert( +y ); // -2 // 轉化非數字 alert( +true ); // 1 alert( +"" ); // 0
它的效果和 Number(...)
相同,但是更加簡短。
我們經常會有將字符串轉化爲數字的需求。比如,如果我們正在從 HTML 表單中取值,通常得到的都是字符串。如果我們想對它們求和,該怎麽辦?
二元運算符加號會把它們合並成字符串:
let apples = "2"; let oranges = "3"; alert( apples + oranges ); // "23",二元運算符加號合並字符串
如果我們想把它們當做數字對待,我們需要轉化它們,然後再求和:
let apples = "2"; let oranges = "3"; // 在二元運算符加號起作用之前,所有的值都被轉化爲了數字 alert( +apples + +oranges ); // 5 // 更長的寫法 // alert( Number(apples) + Number(oranges) ); // 5
從壹個數學家的視角來看,大量的加號可能很奇怪。但是從壹個程序員的視角,沒什麽好奇怪的:壹元運算符加號首先起作用,它們將字符串轉爲數字,然後二元運算符加號對它們進行求和。
爲什麽壹元運算符先于二元運算符作用于運算元?接下去我們將討論到,這是由于它們擁有 更高的優先級。
如果壹個表達式擁有超過壹個運算符,執行的順序則由 優先級 決定。換句話說,所有的運算符中都隱含著優先級順序。
從小學開始,我們就知道在表達式 1 + 2 * 2
中,乘法先于加法計算。這就是壹個優先級問題。乘法比加法擁有 更高的優先級。
圓括號擁有最高優先級,所以如果我們對現有的運算順序不滿意,我們可以使用圓括號來修改運算順序,就像這樣:(1 + 2) * 2
。
在 JavaScript 中有衆多運算符。每個運算符都有對應的優先級數字。數字越大,越先執行。如果優先級相同,則按照由左至右的順序執行。
這是壹個摘抄自 Mozilla 的 優先級表(妳沒有必要把這全記住,但要記住壹元運算符優先級高于二元運算符):
優先級 | 名稱 | 符號 |
---|---|---|
… | … | … |
15 | 壹元加號 | + |
15 | 壹元負號 | - |
14 | 求冪 | ** |
13 | 乘號 | * |
13 | 除號 | / |
12 | 加號 | + |
12 | 減號 | - |
… | … | … |
2 | 賦值符 | = |
… | … | … |
我們可以看到,“壹元加號運算符”的優先級是 15
,高于“二元加號運算符”的優先級 12
。這也是爲什麽表達式 "+apples + +oranges"
中的壹元加號先生效,然後才是二元加法。
我們知道賦值符號 =
也是壹個運算符。從優先級表中可以看到它的優先級非常低,只有 2
。
這也是爲什麽,當我們賦值時,比如 x = 2 * 2 + 1
,所有的計算先執行,然後 =
才執行,將計算結果存儲到 x
。
let x = 2 * 2 + 1; alert( x ); // 5
=
是壹個運算符,而不是壹個有著“魔法”作用的語言結構。
在 JavaScript 中,所有運算符都會返回壹個值。這對于 +
和 -
來說是顯而易見的,但對于 =
來說也是如此。
語句 x = value
將值 value
寫入 x
然後返回 value。
下面是壹個在複雜語句中使用賦值的例子:
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; // 現在 n = 7(等同于 n = n + 5) n *= 2; // 現在 n = 14(等同于 n = n * 2) alert( n ); // 14
所有算術和位運算符都有簡短的“修改並賦值”運算符:/=
和 -=
等。
這類運算符的優先級與普通賦值運算符的優先級相同,所以它們在大多數其他運算之後執行:
let n = 2; n *= 3 + 5; alert( n ); // 16 (右邊部分先被計算,等同于 n *= 8)
對壹個數進行加壹、減壹是最常見的數學運算符之壹。
所以,對此有壹些專門的運算符:
自增 ++
將變量與 1 相加:
let counter = 2; counter++; // 和 counter = counter + 1 效果壹樣,但是更簡潔 alert( counter ); // 3
自減 --
將變量與 1 相減:
let counter = 2; counter--; // 和 counter = counter - 1 效果壹樣,但是更簡潔 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++; // (*) 將 ++counter 改爲 counter++ alert(a); // 1
(*)
所在的行是後置形式 counter++
,它同樣對 counter
做加法,但是返回的是 舊值(做加法之前的值)。因此 alert
顯示的是 1
。
總結:
如果自增/自減的值不會被使用,那麽兩者形式沒有區別:
let counter = 0; counter++; ++counter; alert( counter ); // 2,以上兩行作用相同
如果我們想要對變量進行自增操作,並且 需要立刻使用自增後的值,那麽我們需要使用前置形式:
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,因爲 counter++ 返回的是“舊值”
盡管從技術層面上來說可行,但是這樣的寫法會降低代碼的可閱讀性。在壹行上做多個操作 —— 這樣並不好。
當閱讀代碼時,快速的視覺“縱向”掃描會很容易漏掉 counter++
,這樣的自增操作並不明顯。
我們建議用“壹行壹個行爲”的模式:
let counter = 1; alert( 2 * counter ); counter++;
位運算符把運算元當做 32 位整數,並在它們的二進制表現形式上操作。
這些運算符不是 JavaScript 特有的。大部分的編程語言都支持這些運算符。
下面是位運算符:
按位與 ( &
)
按位或 ( |
)
按位異或 ( ^
)
按位非 ( ~
)
左移 ( <<
)
右移 ( >>
)
無符號右移 ( >>>
)
這些運算符很少被使用,壹般是我們需要在最低級別(位)上操作數字時才使用。我們不會很快用到這些運算符,因爲在 Web 開發中很少使用它們,但在某些特殊領域中,例如密碼學,它們很有用。當妳需要了解它們的時候,可以閱讀 MDN 上的 位操作符 章節。
逗號運算符 ,
是最少見最不常使用的運算符之壹。有時候它會被用來寫更簡短的代碼,因此爲了能夠理解代碼,我們需要了解它。
逗號運算符能讓我們處理多個表達式,使用 ,
將它們分開。每個表達式都運行了,但是只有最後壹個的結果會被返回。
舉個例子:
let a = (1 + 2, 3 + 4); alert( a ); // 7(3 + 4 的結果)
這裏,第壹個表達式 1 + 2
運行了,但是它的結果被丟棄了。隨後計算 3 + 4
,並且該計算結果被返回。
逗號運算符的優先級非常低
請注意逗號運算符的優先級非常低,比 =
還要低,因此上面妳的例子中圓括號非常重要。
如果沒有圓括號:a = 1 + 2, 3 + 4
會先執行 +
,將數值相加得到 a = 3, 7
,然後賦值運算符 =
執行 a = 3
,然後逗號之後的數值 7
不會再執行,它被忽略掉了。相當于 (a = 1 + 2), 3 + 4
。
爲什麽我們需要這樣壹個運算符,它只返回最後壹個值呢?
有時候,人們會使用它把幾個行爲放在壹行上來進行複雜的運算。
舉個例子:
// 壹行上有三個運算符 for (a = 1, b = 3, c = a * b; a < 10; a++) { ... }
這樣的技巧在許多 JavaScript 框架中都有使用,這也是爲什麽我們提到它。但是通常它並不能提升代碼的可讀性,使用它之前,我們要想清楚。
重要程度: 5
以下代碼中變量 a
、b
、c
、d
的最終值分別是多少?
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,前置運算符返回最新值 alert( b++ ); // 1,後置運算符返回舊值 alert( a ); // 2,自增壹次 alert( b ); // 2,自增壹次
重要程度: 3
下面這段代碼運行完成後,代碼中的 a
和 x
的值是多少?
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" + 0
,再次應用同樣的規則得到最終的結果。
減法 -
(像大多數數學運算壹樣)只能用于數字,它會使空字符串 ""
轉換爲 0
。
帶字符串的加法會將數字 5
加到字符串之後。
減法始終將字符串轉換爲數字,因此它會使 " -9 "
轉換爲數字 -9
(忽略了字符串首尾的空格)。
null
經過數字轉換之後會變爲 0
。
undefined
經過數字轉換之後會變爲 NaN
。
字符串轉換爲數字時,會忽略字符串的首尾處的空格字符。在這裏,整個字符串由空格字符組成,包括 t
、n
以及它們之間的“常規”空格。因此,類似于空字符串,所以會變爲 0
。
重要程度: 5
這裏有壹段代碼,要求用戶輸入兩個數字並顯示它們的總和。
它的運行結果不正確。下面例子中的輸出是 12
(對于默認的 prompt 的值)。
爲什麽會這樣?修正它。結果應該是 3
。
let a = prompt("First number?", 1); let b = prompt("Second number?", 2); alert(a + b); // 12
原因是 prompt 以字符串的形式返回用戶的輸入。
所以變量的值分別爲 "1"
和 "2"
。
let a = "1"; // prompt("First number?", 1); let b = "2"; // prompt("Second number?", 2); alert(a + b); // 12
我們應該做的是,在 +
之前將字符串轉換爲數字。例如,使用 Number()
或在 prompt
前加 +
。
例如,就在 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
在最新的代碼中,同時使用壹元和二元的 +
。看起來很有趣,不是嗎?