天天看點

JavaScript 程式設計精解 中文第三版 一、值,類型和運算符

一、值,類型和運算符

原文: Values, Types, and Operators 譯者: 飛龍 協定: CC BY-NC-SA 4.0 自豪地采用 谷歌翻譯 部分參考了 《JavaScript 程式設計精解(第 2 版)》

在機器的表面之下,程式在運轉。 它不費力就可以擴大和縮小。 在和諧的關系中,電子散開并重新聚合。 螢幕上的表格隻是水面上的漣漪。 本質隐藏在下面。

Master Yuan-Ma,《The Book of Programming》

JavaScript 程式設計精解 中文第三版 一、值,類型和運算符

計算機世界裡隻有資料。 你可以讀取資料,修改資料,建立新資料 - 但不能提及不是資料的東西。 所有這些資料都以位的長序列存儲,是以基本相似。

位是任何類型的二值的東西,通常描述為零和一。 在計算機内部,他們有一些形式,例如高電荷或低電荷,強信号或弱信号,或 CD 表面上的亮斑點或暗斑點。 任何一段離散資訊都可以簡化為零和一的序列,進而以位表示。

例如,我們可以用位來表示數字 13。 它的原理與十進制數字相同,但不是 10 個不同的數字,而隻有 2 個,每個數字的權重從右到左增加 2 倍。 以下是組成數字 13 的位,下方顯示數字的權重:

0   0   0   0   1   1   0   1
 128  64  32  16   8   4   2   1           

是以,這就是二進制數

00001101

,或者

8+4+1

,即 13。

想象一下位之海 - 一片它們的海洋。 典型的現代計算機的易失性資料存儲器(工作存儲器)中,有超過 300 億位。非易失性存儲(硬碟或等價物)往往還有幾個數量級。

為了能夠在不丢失的情況下,處理這些數量的資料,我們必須将它們分成代表資訊片段的塊。 在 JavaScript 環境中,這些塊稱為值。 雖然所有值都是由位構成的,但他們起到不同的作用,每個值都有一個決定其作用的類型。 有些值是數字,有些值是文本片段,有些值是函數,等等。

要建立一個值,你隻需要調用它的名字。 這很友善。 你不必為你的值收集建築材料或為其付費。 你隻需要調用它,然後刷的一下,你就有了它。 當然,它們并不是真正憑空創造的。 每個值都必須存儲在某個地方,如果你想同時使用大量的值,則可能會耗盡記憶體。 幸運的是,隻有同時需要它們時,這才是一個問題。 隻要你不再使用值,它就會消失,留下它的一部分作為下一代值的建築材料。

本章将會介紹 JavaScript 程式當中的基本元素,包括簡單的值類型以及值運算符。

數字

數字(

Number

)類型的值即數字值。在 JavaScript 中寫成如下形式:

13           

在程式中使用這個值的時候,就會将數字 13 以位序列的方式存放在計算機的記憶體當中。

JavaScript使用固定數量的位(64 位)來存儲單個數字值。 你可以用 64 位創造很多模式,這意味着可以表示的不同數值是有限的。 對于

N

個十進制數字,可以表示的數值數量是

10^N

。 與之類似,給定 64 個二進制數字,你可以表示

2^64

個不同的數字,大約 18 億億(18 後面有 18 個零)。太多了。

過去計算機記憶體很小,人們傾向于使用一組 8 位或 16 位來表示他們的數字。 這麼小的數字很容易意外地溢出,最終得到的數字不能放在給定的位數中。 今天,即使是裝在口袋裡的電腦也有足夠的記憶體,是以你可以自由使用 64 位的塊,隻有在處理真正的天文數字時才需要擔心溢出。

不過,并非所有 18 億億以下的整數都能放在 JavaScript 數值中。 這些位也存儲負數,是以一位用于表示數字的符号。 一個更大的問題是,也必須表示非整數。 為此,一些位用于存儲小數點的位置。 可以存儲的實際最大整數更多地在 9 億億(15 個零)的範圍内 - 這仍然相當多。

使用小數點來表示分數。

9.81           

對于非常大或非常小的數字,你也可以通過輸入

e

(表示指數),後面跟着指數來使用科學記數法:

2.998e8           

2.998 * 10^8 = 299,800,000

當計算小于前文當中提到的 9000 萬億的整數時,其計算結果會十分精确,不過在計算小數的時候精度卻不高。正如(

pi

)無法使用有限個數的十進制數字表示一樣,在使用 64 位來存儲分數時也同樣會丢失一些精度。雖說如此,但這類丢失精度隻會在一些特殊情況下才會出現問題。是以我們需要注意在處理分數時,将其視為近似值,而非精确值。

算術

與數字密切相關的就是算術。比如,加法或者乘法之類的算術運算會使用兩個數值,并産生一個新的數字。JavaScript 中的算術運算如下所示:

100 + 4 * 11           

我們把

+

*

符号稱為運算符。第一個符号表示加法,第二個符号表示乘法。将一個運算符放在兩個值之間,該運算符将會使用其旁邊的兩個值産生一個新值。

但是這個例子的意思是“将 4 和 100 相加,并将結果乘 11”,還是是在加法之前計算乘法? 正如你可能猜到的那樣,乘法首先計算。 但是和數學一樣,你可以通過将加法包在圓括号中來改變它:

(100 + 4) * 11           

運算符表示減法,

/

運算符則表示除法。

在運算符同時出現,并且沒有括号的情況下,其運算順序根據運算符優先級确定。示例中的乘法運算符優先級高于加法。而

/

運算符和

*

運算符優先級相同,

+

運算符優先級也相同。當多個具有相同優先級的運算符相鄰出現時,運算從左向右執行,比如

1–2+1

的運算順序是

(1–2)+1

你無需擔心這些運算符的優先級規則,不确定的時候隻需要添加括号即可。

還有一個算術運算符,你可能無法立即認出。

%

符号用于表示取餘操作。

X % Y

Y

X

的餘數。 例如,

314 % 100

産生

14

144 % 12

。 餘數的優先級與乘法和除法的優先級相同。 你還經常會看到這個運算符被稱為模運算符。

特殊數字

在 JavaScript 中有三個特殊的值,它們雖然是數字,但看起來卻跟一般的數字不太一樣。

前兩個是

Infinity

-Infinity

,它們代表正無窮和負無窮。 “無窮減一”仍然是“無窮”,依此類推。 盡管如此,不要過分信任基于無窮大的計算。 它在數學上不合理,并且很快導緻我們的下一個特殊數字:

NaN

NaN

代表“不是數字”,即使它是數字類型的值。 例如,當你嘗試計算

0/0

(零除零),

Infinity - Infinity

或任何其他數字操作,它不會産生有意義的結果時,你将得到此結果。

字元串

下一個基本資料類型是字元串(

String

)。 字元串用于表示文本。 它們是用引号括起來的:

`Down on the sea`
"Lie on the ocean"
'Float on the ocean'           

隻要字元串開頭和結尾的引号比對,就可以使用單引号,雙引号或反引号來标記字元串。

幾乎所有的東西都可以放在引号之間,并且 JavaScript 會從中提取字元串值。 但少數字元更難。 你可能難以想象,如何在引号之間加引号。 當使用反引号(

`

)引用字元串時,換行符(當你按Enter鍵時獲得的字元)可能會被包含,而無需轉義。

若要将這些字元存入字元串,需要使用下列規則:當反斜杠(

\

)出現在引号之間的文本中時,表示緊跟在其後的字元具有特殊含義,我們将其稱之為轉義符。當引号緊跟在反斜杠後時,并不意味着字元串結束,而表示這個引号是字元串的一部分。當字元

n

出現在反斜杠後時,JavaScript 将其解釋成換行符。以此類推,

\t

表示制表符,我們來看看下面這個字元串:

"This is the first line\nAnd this is the second"           

該字元串實際表示的文本是:

This is the first line
And this is the second           

當然,在某些情況下,你希望字元串中的反斜杠隻是反斜杠,而不是特殊代碼。 如果兩個反斜杠寫在一起,它們将合并,并且隻有一個将留在結果字元串值中。 這就是字元串“

A newline character is written like "\n".

”的表示方式:

"A newline character is written like \"\\n\"."           

字元串也必須模組化為一系列位,以便能夠存在于計算機内部。 JavaScript 執行此操作的方式基于 Unicode 标準。 該标準為你幾乎需要的每個字元配置設定一個數字,包括來自希臘語,阿拉伯語,日語,亞美尼亞語,以及其他的字元。 如果我們為每個字元配置設定一個數字,則可以用一系列數字來描述一個字元串。

這就是 JavaScript 所做的。 但是有一個複雜的問題:JavaScript 的表示為每個字元串元素使用 16 位,它可以描述多達 2 的 16 次方個不同的字元。 但是,Unicode 定義的字元多于此 - 大約是此處的兩倍。 是以有些字元,比如許多 emoji,在 JavaScript 字元串中占據了兩個“字元位置”。 我們将在第 5 章中回來讨論。

我們不能将除法,乘法或減法運算符用于字元串,但是

+

運算符卻可以。這種情況下,運算符并不表示加法,而是連接配接操作:将兩個字元串連接配接到一起。以下語句可以産生字元串

"concatenate"

"con" + "cat" + "e" + "nate"           

字元串值有許多相關的函數(方法),可用于對它們執行其他操作。 我們将在第 4 章中回來讨論。

用單引号或雙引号編寫的字元串的行為非常相似 - 唯一的差別是需要在其中轉義哪種類型的引号。 反引号字元串,通常稱為模闆字面值,可以實作更多的技巧。 除了能夠跨越行之外,它們還可以嵌入其他值。

`half of 100 is ${100 / 2}`           

當你在模闆字面值中的

$ {}

中寫入内容時,将計算其結果,轉換為字元串并包含在該位置。 這個例子産生

"half of 100 is 50"

一進制運算符

并非所有的運算符都是用符号來表示,還有一些運算符是用單詞表示的。比如

typeof

運算符,會産生一個字元串的值,内容是給定值的具體類型。

console.log(typeof 4.5)
// → number
console.log(typeof "x")
// → string           

我們将在示例代碼中使用

console.log

,來表示我們希望看到求值結果。更多内容請見下一章。

我們所見過的絕大多數運算符都使用兩個值進行操作,而

typeof

僅接受一個值進行操作。使用兩個值的運算符稱為二進制運算符,而使用一個值的則稱為一進制運算符。減号運算符既可用作一進制運算符,也可用作二進制運算符。

console.log(- (10 - 2))
// → -8           

布爾值

擁有一個值,它能區分兩種可能性,通常是有用的,例如“是”和“否”或“開”和“關”。 為此,JavaScript 擁有布爾(

Boolean

)類型,它有兩個值:

true

false

,它們就寫成這些單詞。

比較

一種産生布爾值的方法如下所示:

console.log(3 > 2)
// → true
console.log(3 < 2)
// → false           

>

<

符号分别表示“大于”和“小于”。這兩個符号是二進制運算符,通過該運算符傳回的結果是一個布爾值,表示其運算是否為真。

我們可以使用相同的方法比較字元串。

console.log("Aardvark" < "Zoroaster")
// → true           

字元串排序的方式大緻是字典序,但不真正是你期望從字典中看到的那樣:大寫字母總是比小寫字母“小”,是以

"Z"<"A"

,非字母字元(

!

-

等)也包含在排序中。 比較字元串時,JavaScript 從左向右周遊字元,逐個比較 Unicode 代碼。

其他類似的運算符則包括

>=

(大于等于),

<=

(小于等于),

==

(等于)和

!=

(不等于)。

console.log("Apple" == "Orange")
// → false           

在 JavaScript 中,隻有一個值不等于其自身,那就是

NaN

(Not a Number,非數值)。

console.log(NaN == NaN)
// → false           

NaN

用于表示非法運算的結果,正因如此,不同的非法運算結果也不會相等。

邏輯運算符

還有一些運算符可以應用于布爾值上。JavaScript 支援三種邏輯運算符:與(and),或(or)和非(not)。這些運算符可以用于推理布爾值。

&&

運算符表示邏輯與,該運算符是二進制運算符,隻有當賦給它的兩個值均為

true

時其結果才是真。

console.log(true && false)
// → false
console.log(true && true)
// → true           

||

運算符表示邏輯或。當兩個值中任意一個為

true

時,結果就為真。

console.log(false || true)
// → true
console.log(false || false)
// → false           

感歎号(

!

)表示邏輯非,該運算符是一進制運算符,用于反轉給定的值,比如

!true

的結果是

false

,而

!false

結果是

true

在混合使用布爾運算符和其他運算符的情況下,總是很難确定什麼時候需要使用括号。實際上,隻要熟悉了目前為止我們介紹的運算符,這個問題就不難解決了。

||

優先級最低,其次是

&&

,接着是比較運算符(

>

==

等),最後是其他運算符。基于這些優先級順序,我們在一般情況下最好還是盡量少用括号,比如說:

1 + 1 == 2 && 10 * 10 > 50           

現在我們來讨論最後一個邏輯運算符,它既不屬于一進制運算符,也不屬于二進制運算符,而是三元運算符(同時操作三個值)。該運算符由一個問号和冒号組成,如下所示。

console.log(true ? 1 : 2);
// → 1
console.log(false ? 1 : 2);
// → 2           

這個被稱為條件運算符(或者有時候隻是三元運算符,因為它是該語言中唯一的這樣的運算符)。 問号左側的值“挑選”另外兩個值中的一個。 當它為真,它選擇中間的值,當它為假,則是右邊的值。

空值

有兩個特殊值,寫成

null

undefined

,用于表示不存在有意義的值。 它們本身就是值,但它們沒有任何資訊。

在 JavaScript 語言中,有許多操作都會産生無意義的值(我們會在後面的内容中看到執行個體),這些操作會得到

undefined

的結果僅僅隻是因為每個操作都必須産生一個值。

undefined

null

之間的意義差異是 JavaScript 設計的一個意外,大多數時候它并不重要。 在你實際上不得不關注這些值的情況下,我建議将它們視為幾乎可互換的。

自動類型轉換

在引言中,我提到 JavaScript 會盡可能接受幾乎所有你給他的程式,甚至是那些做些奇怪事情的程式。 以下表達式很好地證明了這一點:

console.log(8 * null)
// → 0
console.log("5" - 1)
// → 4
console.log("5" + 1)
// → 51
console.log("five" * 2)
// → NaN
console.log(false == 0)
// → true           

當運算符應用于類型“錯誤”的值時,JavaScript 會悄悄地将該值轉換為所需的類型,并使用一組通常不是你想要或期望的規則。 這稱為類型轉換。 第一個表達式中的

null

變為

,第二個表達式中的

"5"

5

(從字元串到數字)。 然而在第三個表達式中,

+

在數字加法之前嘗試字元串連接配接,是以

1

被轉換為

"1"

(從數字到字元串)。

當某些不能明顯映射為數字的東西(如

"five"

undefined

)轉換為數字時,你會得到值

NaN

NaN

進一步的算術運算會産生

NaN

,是以如果你發現自己在一個意想不到的地方得到了它,需要尋找意外的類型轉換。

當相同類型的值之間使用

==

符号進行比較時,其運算結果很好預測:除了

NaN

這種情況,隻要兩個值相同,則傳回

true

。但如果類型不同,JavaScript 則會使用一套複雜難懂的規則來确定輸出結果。在絕大多數情況下,JavaScript 隻是将其中一個值轉換成另一個值的類型。但如果運算符兩側存在

null

undefined

,那麼隻有兩側均為

null

undefined

時結果才為

true

console.log(null == undefined);
// → true
console.log(null == 0);
// → false           

這種行為通常很有用。 當你想測試一個值是否具有真值而不是

null

undefined

時,你可以用

==

(或

!=

)運算符将它與

null

進行比較。

但是如果你想測試某些東西是否嚴格為“false”呢? 字元串和數字轉換為布爾值的規則表明,

NaN

和空字元串(

""

)計為

false

,而其他所有值都計為

true

。 是以,像

'0 == false'

"" == false

這樣的表達式也是真的。 當你不希望發生自動類型轉換時,還有兩個額外的運算符:

===

!==

。 第一個測試是否嚴格等于另一個值,第二個測試它是否不嚴格相等。 是以

"" === false

如預期那樣是錯誤的。

我建議使用三字元比較運算符來防止意外類型轉換的發生,避免作繭自縛。但如果比較運算符兩側的值類型是相同的,那麼使用較短的運算符也沒有問題。

邏輯運算符的短路特性

&&

||

以一種特殊的方式處理不同類型的值。 他們會将其左側的值轉換為布爾型,來決定要做什麼,但根據運算符和轉換結果,它們将傳回原始的左側值或右側值。

例如,當左側值可以轉換為

true

時,

||

運算符會傳回它,否則傳回右側值。 當值為布爾值時,這具有預期的效果,并且對其他類型的值做類似的操作。

console.log(null || "user")
// → user
console.log("Agnes" || "user")
// → Agnes           

我們可以此功能用作回落到預設值的方式。 如果你的一個值可能是空的,你可以把

||

和備選值放在它之後。 如果初始值可以轉換為

false

,那麼你将得到備選值。

&&

運算符工作方式與其相似但不相同。當左側的值可以被轉換成

false

&&

運算符會傳回左側值,否則傳回右側值。

這兩個運算符的另一個重要特性是,隻在必要時求解其右側的部分。 在

true || X

的情況下,不管

X

是什麼 - 即使它是一個執行某些惡意操作的程式片段,結果都是

true

,并且

X

永遠不會求值。

false && X

也是一樣,它是

false

的,并且忽略

X

。 這稱為短路求值。

條件運算符以類似的方式工作。 在第二個和第三個值中,隻有被選中的值才會求值。

本章小結

在本章中,我們介紹了 JavaScript 的四種類型的值:數字,字元串,布爾值和未定義值。

通過輸入值的名稱(

true

null

)或值(

13

"abc"

)就可以建立它們。你還可以通過運算符來對值進行合并和轉換操作。本章已經介紹了算術二進制運算符(

+

*

/

%

),字元串連接配接符(

+

),比較運算符(

==

!=

===

!==

<

>

<=

>=

),邏輯運算符(

&&

||

)和一些一進制運算符(

表示負數,

!

表示邏輯非,

typeof

用于查詢值的類型)。

這為你提供了足夠的資訊,将 JavaScript 用作便攜式電腦,但并不多。 下一章将開始将這些表達式綁定到基本程式中。

繼續閱讀