Javascript 基礎(chǔ)運算符,數(shù)學(xué)運算

2023-02-17 10:37 更新

我們從學(xué)校里了解到過很多運算符,比如說加號 ?+?、乘號 ?*?、減號 ?-? 等。

在本章中,我們將從簡單的運算符開始,然后著重介紹 JavaScript 特有的方面,這些是在學(xué)校中學(xué)習(xí)的數(shù)學(xué)運算所沒有涵蓋的。

術(shù)語:“一元運算符”,“二元運算符”,“運算元”

在正式開始前,我們先簡單瀏覽一下常用術(shù)語。

  • 運算元 —— 運算符應(yīng)用的對象。比如說乘法運算 ?5 * 2?,有兩個運算元:左運算元 ?5? 和右運算元 ?2?。有時候人們也稱其為“參數(shù)”而不是“運算元”。
  • 如果一個運算符對應(yīng)的只有一個運算元,那么它是 一元運算符。比如說一元負(fù)號運算符(unary negation)?-?,它的作用是對數(shù)字進行正負(fù)轉(zhuǎn)換:
  • let x = 1;
    
    x = -x;
    alert( x ); // -1,一元負(fù)號運算符生效
  • 如果一個運算符擁有兩個運算元,那么它是 二元運算符。減號還存在二元運算符形式:
  • let x = 1, y = 3;
    alert( y - x ); // 2,二元運算符減號做減運算

    嚴(yán)格地說,在上面的示例中,我們使用一個相同的符號表征了兩個不同的運算符:負(fù)號運算符,即反轉(zhuǎn)符號的一元運算符,減法運算符,是從另一個數(shù)減去一個數(shù)的二元運算符。

數(shù)學(xué)運算

支持以下數(shù)學(xué)運算:

  • 加法 ?+?,
  • 減法 ?-?,
  • 乘法 ?*?,
  • 除法 ?/?,
  • 取余 ?%?,
  • 求冪 ?**?.

前四個都很簡單,而 ?%? 和 ?**? 則需要說一說。

取余 %

取余運算符是 %,盡管它看起來很像百分?jǐn)?shù),但實際并無關(guān)聯(lián)。

a % b 的結(jié)果是 a 整除 b 的 余數(shù))。

例如:

alert( 5 % 2 ); // 1,5 除以 2 的余數(shù)
alert( 8 % 3 ); // 2,8 除以 3 的余數(shù)

求冪 **

求冪運算 a ** b 將 a 提升至 a 的 b 次冪。

在數(shù)學(xué)運算中我們將其表示為 ab

例如:

alert( 2 ** 2 ); // 22 = 4
alert( 2 ** 3 ); // 23 = 8
alert( 2 ** 4 ); // 2? = 16

就像在數(shù)學(xué)運算中一樣,冪運算也適用于非整數(shù)。

例如,平方根是指數(shù)為 ? 的冪運算:

alert( 4 ** (1/2) ); // 2(1/2 次方與平方根相同)
alert( 8 ** (1/3) ); // 2(1/3 次方與立方根相同)

用二元運算符 + 連接字符串

我們來看一些學(xué)校算術(shù)未涉及的 JavaScript 運算符的特性。

通常,加號 + 用于求和。

但是如果加號 + 被應(yīng)用于字符串,它將合并(連接)各個字符串:

let s = "my" + "string";
alert(s); // mystring

注意:只要任意一個運算元是字符串,那么另一個運算元也將被轉(zhuǎn)化為字符串。

舉個例子:

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"

你看,第一個運算元和第二個運算元,哪個是字符串并不重要。

下面是一個更復(fù)雜的例子:

alert(2 + 2 + '1' ); // "41",不是 "221"

在這里,運算符是按順序工作。第一個 + 將兩個數(shù)字相加,所以返回 4,然后下一個 + 將字符串 1 加入其中,所以就是 4 + '1' = '41'。

alert('1' + 2 + 2); // "122",不是 "14"

這里,第一個操作數(shù)是一個字符串,所以編譯器將其他兩個操作數(shù)也視為了字符串。2 被與 '1' 連接到了一起,也就是像 '1' + 2 = "12" 然后 "12" + 2 = "122" 這樣。

二元 + 是唯一一個以這種方式支持字符串的運算符。其他算術(shù)運算符只對數(shù)字起作用,并且總是將其運算元轉(zhuǎn)換為數(shù)字。

下面是減法和除法運算的示例:

alert( 6 - '2' ); // 4,將 '2' 轉(zhuǎn)換為數(shù)字
alert( '6' / '2' ); // 3,將兩個運算元都轉(zhuǎn)換為數(shù)字

數(shù)字轉(zhuǎn)化,一元運算符 +

加號 + 有兩種形式。一種是上面我們剛剛討論的二元運算符,還有一種是一元運算符。

一元運算符加號,或者說,加號 + 應(yīng)用于單個值,對數(shù)字沒有任何作用。但是如果運算元不是數(shù)字,加號 + 則會將其轉(zhuǎn)化為數(shù)字。

例如:

// 對數(shù)字無效
let x = 1;
alert( +x ); // 1

let y = -2;
alert( +y ); // -2

// 轉(zhuǎn)化非數(shù)字
alert( +true ); // 1
alert( +"" );   // 0

它的效果和 Number(...) 相同,但是更加簡短。

我們經(jīng)常會有將字符串轉(zhuǎn)化為數(shù)字的需求。比如,如果我們正在從 HTML 表單中取值,通常得到的都是字符串。如果我們想對它們求和,該怎么辦?

二元運算符加號會把它們合并成字符串:

let apples = "2";
let oranges = "3";

alert( apples + oranges ); // "23",二元運算符加號合并字符串

如果我們想把它們當(dāng)做數(shù)字對待,我們需要轉(zhuǎn)化它們,然后再求和:

let apples = "2";
let oranges = "3";

// 在二元運算符加號起作用之前,所有的值都被轉(zhuǎn)化為了數(shù)字
alert( +apples + +oranges ); // 5

// 更長的寫法
// alert( Number(apples) + Number(oranges) ); // 5

從一個數(shù)學(xué)家的視角來看,大量的加號可能很奇怪。但是從一個程序員的視角,沒什么好奇怪的:一元運算符加號首先起作用,它們將字符串轉(zhuǎn)為數(shù)字,然后二元運算符加號對它們進行求和。

為什么一元運算符先于二元運算符作用于運算元?接下去我們將討論到,這是由于它們擁有 更高的優(yōu)先級

運算符優(yōu)先級

如果一個表達(dá)式擁有超過一個運算符,執(zhí)行的順序則由 優(yōu)先級 決定。換句話說,所有的運算符中都隱含著優(yōu)先級順序。

從小學(xué)開始,我們就知道在表達(dá)式 1 + 2 * 2 中,乘法先于加法計算。這就是一個優(yōu)先級問題。乘法比加法擁有 更高的優(yōu)先級。

圓括號擁有最高優(yōu)先級,所以如果我們對現(xiàn)有的運算順序不滿意,我們可以使用圓括號來修改運算順序,就像這樣:(1 + 2) * 2。

在 JavaScript 中有眾多運算符。每個運算符都有對應(yīng)的優(yōu)先級數(shù)字。數(shù)字越大,越先執(zhí)行。如果優(yōu)先級相同,則按照由左至右的順序執(zhí)行。

這是一個摘抄自 Mozilla 的 優(yōu)先級表(你沒有必要把這全記住,但要記住一元運算符優(yōu)先級高于二元運算符):

優(yōu)先級 名稱 符號
15 一元加號 +
15 一元負(fù)號 -
14 求冪 **
13 乘號 *
13 除號 /
12 加號 +
12 減號 -
2 賦值符 =

我們可以看到,“一元加號運算符”的優(yōu)先級是 15,高于“二元加號運算符”的優(yōu)先級 12。這也是為什么表達(dá)式 "+apples + +oranges" 中的一元加號先生效,然后才是二元加法。

賦值運算符

我們知道賦值符號 = 也是一個運算符。從優(yōu)先級表中可以看到它的優(yōu)先級非常低,只有 2。

這也是為什么,當(dāng)我們賦值時,比如 x = 2 * 2 + 1,所有的計算先執(zhí)行,然后 = 才執(zhí)行,將計算結(jié)果存儲到 x。

let x = 2 * 2 + 1;

alert( x ); // 5

賦值 = 返回一個值

= 是一個運算符,而不是一個有著“魔法”作用的語言結(jié)構(gòu)。

在 JavaScript 中,所有運算符都會返回一個值。這對于 + 和 - 來說是顯而易見的,但對于 = 來說也是如此。

語句 x = value 將值 value 寫入 x 然后返回 value。

下面是一個在復(fù)雜語句中使用賦值的例子:

let a = 1;
let b = 2;

let c = 3 - (a = b + 1);

alert( a ); // 3
alert( c ); // 0

上面這個例子,(a = b + 1) 的結(jié)果是賦給 a 的值(也就是 3)。然后該值被用于進一步的運算。

有趣的代碼,不是嗎?我們應(yīng)該了解它的工作原理,因為有時我們會在 JavaScript 庫中看到它。

不過,請不要寫這樣的代碼。這樣的技巧絕對不會使代碼變得更清晰或可讀。

鏈?zhǔn)劫x值(Chaining assignments)

另一個有趣的特性是鏈?zhǔn)劫x值:

let a, b, c;

a = b = c = 2 + 2;

alert( a ); // 4
alert( b ); // 4
alert( c ); // 4

鏈?zhǔn)劫x值從右到左進行計算。首先,對最右邊的表達(dá)式 2 + 2 求值,然后將其賦給左邊的變量:c、b 和 a。最后,所有的變量共享一個值。

同樣,出于可讀性,最好將這種代碼分成幾行:

c = 2 + 2;
b = c;
a = c;

這樣可讀性更強,尤其是在快速瀏覽代碼的時候。

原地修改

我們經(jīng)常需要對一個變量做運算,并將新的結(jié)果存儲在同一個變量中。

例如:

let n = 2;
n = n + 5;
n = n * 2;

可以使用運算符 += 和 *= 來縮寫這種表示。

let n = 2;
n += 5; // 現(xiàn)在 n = 7(等同于 n = n + 5)
n *= 2; // 現(xiàn)在 n = 14(等同于 n = n * 2)

alert( n ); // 14

所有算術(shù)和位運算符都有簡短的“修改并賦值”運算符:/= 和 -= 等。

這類運算符的優(yōu)先級與普通賦值運算符的優(yōu)先級相同,所以它們在大多數(shù)其他運算之后執(zhí)行:

let n = 2;

n *= 3 + 5;

alert( n ); // 16 (右邊部分先被計算,等同于 n *= 8)

自增/自減

對一個數(shù)進行加一、減一是最常見的數(shù)學(xué)運算符之一。

所以,對此有一些專門的運算符:

  • 自增 ?++? 將變量與 1 相加:
  • let counter = 2;
    counter++;      // 和 counter = counter + 1 效果一樣,但是更簡潔
    alert( counter ); // 3
  • 自減 -- 將變量與 1 相減:
  • let counter = 2;
    counter--;      // 和 counter = counter - 1 效果一樣,但是更簡潔
    alert( counter ); // 1

重要:

自增/自減只能應(yīng)用于變量。試一下,將其應(yīng)用于數(shù)值(比如 5++)則會報錯。

運算符 ++ 和 -- 可以置于變量前,也可以置于變量后。

  • 當(dāng)運算符置于變量后,被稱為“后置形式”:?counter++?。
  • 當(dāng)運算符置于變量前,被稱為“前置形式”:?++counter?。

兩者都做同一件事:將變量 counter 與 1 相加。

那么它們有區(qū)別嗎?有,但只有當(dāng)我們使用 ++/-- 的返回值時才能看到區(qū)別。

詳細(xì)點說。我們知道,所有的運算符都有返回值。自增/自減也不例外。前置形式返回一個新的值,但后置返回原來的值(做加法/減法之前的值)。

為了直觀看到區(qū)別,看下面的例子:

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。

總結(jié):

  • 如果自增/自減的值不會被使用,那么兩者形式?jīng)]有區(qū)別:
  • let counter = 0;
    counter++;
    ++counter;
    alert( counter ); // 2,以上兩行作用相同
  • 如果我們想要對變量進行自增操作,并且 需要立刻使用自增后的值,那么我們需要使用前置形式:
  • let counter = 0;
    alert( ++counter ); // 1
  • 如果我們想要將一個數(shù)加一,但是我們想使用其自增之前的值,那么我們需要使用后置形式:
  • let counter = 0;
    alert( counter++ ); // 0

自增/自減和其它運算符的對比

++/-- 運算符同樣可以在表達(dá)式內(nèi)部使用。它們的優(yōu)先級比絕大部分的算數(shù)運算符要高。

舉個例子:

let counter = 1;
alert( 2 * ++counter ); // 4

與下方例子對比:

let counter = 1;
alert( 2 * counter++ ); // 2,因為 counter++ 返回的是“舊值”

盡管從技術(shù)層面上來說可行,但是這樣的寫法會降低代碼的可閱讀性。在一行上做多個操作 —— 這樣并不好。

當(dāng)閱讀代碼時,快速的視覺“縱向”掃描會很容易漏掉 counter++,這樣的自增操作并不明顯。

我們建議用“一行一個行為”的模式:

let counter = 1;
alert( 2 * counter );
counter++;

位運算符

位運算符把運算元當(dāng)做 32 位整數(shù),并在它們的二進制表現(xiàn)形式上操作。

這些運算符不是 JavaScript 特有的。大部分的編程語言都支持這些運算符。

下面是位運算符:

  • 按位與 ( ?&? )
  • 按位或 ( ?|? )
  • 按位異或 ( ?^? )
  • 按位非 ( ?~? )
  • 左移 ( ?<<? )
  • 右移 ( ?>>? )
  • 無符號右移 ( ?>>>? )

這些運算符很少被使用,一般是我們需要在最低級別(位)上操作數(shù)字時才使用。我們不會很快用到這些運算符,因為在 Web 開發(fā)中很少使用它們,但在某些特殊領(lǐng)域中,例如密碼學(xué),它們很有用。當(dāng)你需要了解它們的時候,可以閱讀 MDN 上的 位操作符 章節(jié)。

逗號運算符

逗號運算符 , 是最少見最不常使用的運算符之一。有時候它會被用來寫更簡短的代碼,因此為了能夠理解代碼,我們需要了解它。

逗號運算符能讓我們處理多個語句,使用 , 將它們分開。每個語句都運行了,但是只有最后的語句的結(jié)果會被返回。

舉個例子:

let a = (1 + 2, 3 + 4);

alert( a ); // 7(3 + 4 的結(jié)果)

這里,第一個語句 1 + 2 運行了,但是它的結(jié)果被丟棄了。隨后計算 3 + 4,并且該計算結(jié)果被返回。

逗號運算符的優(yōu)先級非常低

請注意逗號運算符的優(yōu)先級非常低,比 = 還要低,因此上面你的例子中圓括號非常重要。

如果沒有圓括號:a = 1 + 2, 3 + 4 會先執(zhí)行 +,將數(shù)值相加得到 a = 3, 7,然后賦值運算符 = 執(zhí)行 a = 3,然后逗號之后的數(shù)值 7 不會再執(zhí)行,它被忽略掉了。相當(dāng)于 (a = 1 + 2), 3 + 4。

為什么我們需要這樣一個運算符,它只返回最后一個值呢?

有時候,人們會使用它把幾個行為放在一行上來進行復(fù)雜的運算。

舉個例子:

// 一行上有三個運算符
for (a = 1, b = 3, c = a * b; a < 10; a++) {
 ...
}

這樣的技巧在許多 JavaScript 框架中都有使用,這也是為什么我們提到它。但是通常它并不能提升代碼的可讀性,使用它之前,我們要想清楚。

任務(wù)


后置運算符和前置運算符

重要程度: 5

以下代碼中變量 a、bc、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,自增一次

賦值結(jié)果

重要程度: 3

下面這段代碼運行完成后,代碼中的 a 和 x 的值是多少?

let a = 2;

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

解決方案

答案如下:

  • ?a = 4?(乘以 2)
  • ?x = 5?(相當(dāng)于計算 1 + 4)

類型轉(zhuǎn)換

重要程度: 5

下面這些表達(dá)式的結(jié)果是什么?

"" + 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?,首先會將數(shù)字 ?1? 轉(zhuǎn)換為一個字符串:?"" + 1 = "1"?,然后我們得到 ?"1" + 0?,再次應(yīng)用同樣的規(guī)則得到最終的結(jié)果。
  2. 減法 ?-?(像大多數(shù)數(shù)學(xué)運算一樣)只能用于數(shù)字,它會使空字符串 ?""? 轉(zhuǎn)換為 ?0?。
  3. 帶字符串的加法會將數(shù)字 ?5? 加到字符串之后。
  4. 減法始終將字符串轉(zhuǎn)換為數(shù)字,因此它會使 ?" -9 "? 轉(zhuǎn)換為數(shù)字 ?-9?(忽略了字符串首尾的空格)。
  5. ?null ?經(jīng)過數(shù)字轉(zhuǎn)換之后會變?yōu)?nbsp;?0?。
  6. ?undefined ?經(jīng)過數(shù)字轉(zhuǎn)換之后會變?yōu)?nbsp;?NaN?。
  7. 字符串轉(zhuǎn)換為數(shù)字時,會忽略字符串的首尾處的空格字符。在這里,整個字符串由空格字符組成,包括 ?\t?、?\n? 以及它們之間的“常規(guī)”空格。因此,類似于空字符串,所以會變?yōu)?nbsp;?0?。

修正加法

重要程度: 5

這里有一段代碼,要求用戶輸入兩個數(shù)字并顯示它們的總和。

它的運行結(jié)果不正確。下面例子中的輸出是 12(對于默認(rèn)的 prompt 的值)。

為什么會這樣?修正它。結(jié)果應(yīng)該是 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

我們應(yīng)該做的是,在 + 之前將字符串轉(zhuǎn)換為數(shù)字。例如,使用 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

在最新的代碼中,同時使用一元和二元的 +??雌饋砗苡腥?,不是嗎?


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號