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

一、值,類型和運算符

原文:Values, Types, and Operators

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

自豪地采用谷歌翻譯

部分參考了《JavaScript 編程精解(第 2 版)》

在機器的表面之下,程序在運轉(zhuǎn)。 它不費力就可以擴大和縮小。 在和諧的關(guān)系中,電子散開并重新聚合。 監(jiān)視器上的表格只是水面上的漣漪。 本質(zhì)隱藏在下面。

Master Yuan-Ma,《The Book of Programming》

image

計算機世界里只有數(shù)據(jù)。 你可以讀取數(shù)據(jù),修改數(shù)據(jù),創(chuàng)建新數(shù)據(jù) - 但不能提及不是數(shù)據(jù)的東西。 所有這些數(shù)據(jù)都以位的長序列存儲,因此基本相似。

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

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

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

因此,這就是二進制數(shù)00001101,或者8+4+1,即 13。

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

為了能夠在不丟失的情況下,處理這些數(shù)量的數(shù)據(jù),我們必須將它們分成代表信息片段的塊。 在 JavaScript 環(huán)境中,這些塊稱為值。 雖然所有值都是由位構(gòu)成的,但他們起到不同的作用,每個值都有一個決定其作用的類型。 有些值是數(shù)字,有些值是文本片段,有些值是函數(shù),等等。

要創(chuàng)建一個值,你只需要調(diào)用它的名字。 這很方便。 你不必為你的值收集建筑材料或為其付費。 你只需要調(diào)用它,然后刷的一下,你就有了它。 當然,它們并不是真正憑空創(chuàng)造的。 每個值都必須存儲在某個地方,如果你想同時使用大量的值,則可能會耗盡內(nèi)存。 幸運的是,只有同時需要它們時,這才是一個問題。 只要你不再使用值,它就會消失,留下它的一部分作為下一代值的建筑材料。

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

數(shù)字

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

13

在程序中使用這個值的時候,就會將數(shù)字 13 以位序列的方式存放在計算機的內(nèi)存當中。

JavaScript使用固定數(shù)量的位(64 位)來存儲單個數(shù)字值。 你可以用 64 位創(chuàng)造很多模式,這意味著可以表示的不同數(shù)值是有限的。 對于N個十進制數(shù)字,可以表示的數(shù)值數(shù)量是10^N。 與之類似,給定 64 個二進制數(shù)字,你可以表示2^64個不同的數(shù)字,大約 18 億億(18 后面有 18 個零)。太多了。

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

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

使用小數(shù)點來表示分數(shù)。

9.81

對于非常大或非常小的數(shù)字,你也可以通過輸入e(表示指數(shù)),后面跟著指數(shù)來使用科學記數(shù)法:

2.998e8

2.998 * 10^8 = 299,800,000。

當計算小于前文當中提到的 9000 萬億的整數(shù)時,其計算結(jié)果會十分精確,不過在計算小數(shù)的時候精度卻不高。正如(pi)無法使用有限個數(shù)的十進制數(shù)字表示一樣,在使用 64 位來存儲分數(shù)時也同樣會丟失一些精度。雖說如此,但這類丟失精度只會在一些特殊情況下才會出現(xiàn)問題。因此我們需要注意在處理分數(shù)時,將其視為近似值,而非精確值。

算術(shù)

與數(shù)字密切相關(guān)的就是算術(shù)。比如,加法或者乘法之類的算術(shù)運算會使用兩個數(shù)值,并產(chǎn)生一個新的數(shù)字。JavaScript 中的算術(shù)運算如下所示:

100 + 4 * 11

我們把+*符號稱為運算符。第一個符號表示加法,第二個符號表示乘法。將一個運算符放在兩個值之間,該運算符將會使用其旁邊的兩個值產(chǎn)生一個新值。

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

(100 + 4) * 11

運算符表示減法,/運算符則表示除法。

在運算符同時出現(xiàn),并且沒有括號的情況下,其運算順序根據(jù)運算符優(yōu)先級確定。示例中的乘法運算符優(yōu)先級高于加法。而/運算符和*運算符優(yōu)先級相同,+運算符和運算符優(yōu)先級也相同。當多個具有相同優(yōu)先級的運算符相鄰出現(xiàn)時,運算從左向右執(zhí)行,比如1–2+1的運算順序是(1–2)+1。

你無需擔心這些運算符的優(yōu)先級規(guī)則,不確定的時候只需要添加括號即可。

還有一個算術(shù)運算符,你可能無法立即認出。 %符號用于表示取余操作。 X % YYX的余數(shù)。 例如,314 % 100產(chǎn)生14,144 % 12產(chǎn)生0。 余數(shù)的優(yōu)先級與乘法和除法的優(yōu)先級相同。 你還經(jīng)常會看到這個運算符被稱為模運算符。

特殊數(shù)字

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

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

NaN代表“不是數(shù)字”,即使它是數(shù)字類型的值。 例如,當你嘗試計算0/0(零除零),Infinity - Infinity或任何其他數(shù)字操作,它不會產(chǎn)生有意義的結(jié)果時,你將得到此結(jié)果。

字符串

下一個基本數(shù)據(jù)類型是字符串(String)。 字符串用于表示文本。 它們是用引號括起來的:

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

只要字符串開頭和結(jié)尾的引號匹配,就可以使用單引號,雙引號或反引號來標記字符串。

幾乎所有的東西都可以放在引號之間,并且 JavaScript 會從中提取字符串值。 但少數(shù)字符更難。 你可能難以想象,如何在引號之間加引號。 當使用反引號(`)引用字符串時,換行符(當你按回車鍵時獲得的字符)可能會被包含,而無需轉(zhuǎn)義。

若要將這些字符存入字符串,需要使用下列規(guī)則:當反斜杠(\)出現(xiàn)在引號之間的文本中時,表示緊跟在其后的字符具有特殊含義,我們將其稱之為轉(zhuǎn)義符。當引號緊跟在反斜杠后時,并不意味著字符串結(jié)束,而表示這個引號是字符串的一部分。當字符n出現(xiàn)在反斜杠后時,JavaScript 將其解釋成換行符。以此類推,\t表示制表符,我們來看看下面這個字符串:

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

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

This is the first line
And this is the second

當然,在某些情況下,你希望字符串中的反斜杠只是反斜杠,而不是特殊代碼。 如果兩個反斜杠寫在一起,它們將合并,并且只有一個將留在結(jié)果字符串值中。 這就是字符串“A newline character is written like "\n".”的表示方式:

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

字符串也必須建模為一系列位,以便能夠存在于計算機內(nèi)部。 JavaScript 執(zhí)行此操作的方式基于 Unicode 標準。 該標準為你幾乎需要的每個字符分配一個數(shù)字,包括來自希臘語,阿拉伯語,日語,亞美尼亞語,以及其他的字符。 如果我們?yōu)槊總€字符分配一個數(shù)字,則可以用一系列數(shù)字來描述一個字符串。

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

我們不能將除法,乘法或減法運算符用于字符串,但是+運算符卻可以。這種情況下,運算符并不表示加法,而是連接操作:將兩個字符串連接到一起。以下語句可以產(chǎn)生字符串"concatenate"

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

字符串值有許多相關(guān)的函數(shù)(方法),可用于對它們執(zhí)行其他操作。 我們將在第 4 章中回來討論。

用單引號或雙引號編寫的字符串的行為非常相似 - 唯一的區(qū)別是需要在其中轉(zhuǎn)義哪種類型的引號。 反引號字符串,通常稱為模板字面值,可以實現(xiàn)更多的技巧。 除了能夠跨越行之外,它們還可以嵌入其他值。

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

當你在模板字面值中的$ {}中寫入內(nèi)容時,將計算其結(jié)果,轉(zhuǎn)換為字符串并包含在該位置。 這個例子產(chǎn)生"half of 100 is 50"。

一元運算符

并非所有的運算符都是用符號來表示,還有一些運算符是用單詞表示的。比如typeof運算符,會產(chǎn)生一個字符串的值,內(nèi)容是給定值的具體類型。

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

我們將在示例代碼中使用console.log,來表示我們希望看到求值結(jié)果。更多內(nèi)容請見下一章。

我們所見過的絕大多數(shù)運算符都使用兩個值進行操作,而typeof僅接受一個值進行操作。使用兩個值的運算符稱為二元運算符,而使用一個值的則稱為一元運算符。減號運算符既可用作一元運算符,也可用作二元運算符。

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

布爾值

擁有一個值,它能區(qū)分兩種可能性,通常是有用的,例如“是”和“否”或“開”和“關(guān)”。 為此,JavaScript 擁有布爾(Boolean)類型,它有兩個值:truefalse,它們就寫成這些單詞。

比較

一種產(chǎn)生布爾值的方法如下所示:

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

><符號分別表示“大于”和“小于”。這兩個符號是二元運算符,通過該運算符返回的結(jié)果是一個布爾值,表示其運算是否為真。

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

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

字符串排序的方式大致是字典序,但不真正是你期望從字典中看到的那樣:大寫字母總是比小寫字母“小”,所以"Z"<"A",非字母字符(!-等)也包含在排序中。 比較字符串時,JavaScript 從左向右遍歷字符,逐個比較 Unicode 代碼。

其他類似的運算符則包括>=(大于等于),<=(小于等于),==(等于)和!=(不等于)。

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

在 JavaScript 中,只有一個值不等于其自身,那就是NaN(Not a Number,非數(shù)值)。

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

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

邏輯運算符

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

&&運算符表示邏輯與,該運算符是二元運算符,只有當賦給它的兩個值均為true時其結(jié)果才是真。

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

||運算符表示邏輯或。當兩個值中任意一個為true時,結(jié)果就為真。

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

感嘆號(!)表示邏輯非,該運算符是一元運算符,用于反轉(zhuǎn)給定的值,比如!true的結(jié)果是false,而!false結(jié)果是true。

在混合使用布爾運算符和其他運算符的情況下,總是很難確定什么時候需要使用括號。實際上,只要熟悉了目前為止我們介紹的運算符,這個問題就不難解決了。||優(yōu)先級最低,其次是&&,接著是比較運算符(>,==等),最后是其他運算符?;谶@些優(yōu)先級順序,我們在一般情況下最好還是盡量少用括號,比如說:

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

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

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

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

空值

有兩個特殊值,寫成nullundefined,用于表示不存在有意義的值。 它們本身就是值,但它們沒有任何信息。

在 JavaScript 語言中,有許多操作都會產(chǎn)生無意義的值(我們會在后面的內(nèi)容中看到實例),這些操作會得到undefined的結(jié)果僅僅只是因為每個操作都必須產(chǎn)生一個值。

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

自動類型轉(zhuǎn)換

在引言中,我提到 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

當運算符應(yīng)用于類型“錯誤”的值時,JavaScript 會悄悄地將該值轉(zhuǎn)換為所需的類型,并使用一組通常不是你想要或期望的規(guī)則。 這稱為類型轉(zhuǎn)換。 第一個表達式中的null變?yōu)?code>0,第二個表達式中的"5"變?yōu)?code>5(從字符串到數(shù)字)。 然而在第三個表達式中,+在數(shù)字加法之前嘗試字符串連接,所以1被轉(zhuǎn)換為"1"(從數(shù)字到字符串)。

當某些不能明顯映射為數(shù)字的東西(如"five"undefined)轉(zhuǎn)換為數(shù)字時,你會得到值NaN。 NaN進一步的算術(shù)運算會產(chǎn)生NaN,所以如果你發(fā)現(xiàn)自己在一個意想不到的地方得到了它,需要尋找意外的類型轉(zhuǎn)換。

當相同類型的值之間使用==符號進行比較時,其運算結(jié)果很好預測:除了NaN這種情況,只要兩個值相同,則返回true。但如果類型不同,JavaScript 則會使用一套復雜難懂的規(guī)則來確定輸出結(jié)果。在絕大多數(shù)情況下,JavaScript 只是將其中一個值轉(zhuǎn)換成另一個值的類型。但如果運算符兩側(cè)存在nullundefined,那么只有兩側(cè)均為nullundefined時結(jié)果才為true

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

這種行為通常很有用。 當你想測試一個值是否具有真值而不是nullundefined時,你可以用==(或!=)運算符將它與null進行比較。

但是如果你想測試某些東西是否嚴格為“false”呢? 字符串和數(shù)字轉(zhuǎn)換為布爾值的規(guī)則表明,0,NaN和空字符串("")計為false,而其他所有值都計為true。 因此,像'0 == false'"" == false這樣的表達式也是真的。 當你不希望發(fā)生自動類型轉(zhuǎn)換時,還有兩個額外的運算符:===!==。 第一個測試是否嚴格等于另一個值,第二個測試它是否不嚴格相等。 所以"" === false如預期那樣是錯誤的。

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

邏輯運算符的短路特性

邏輯運算符&&||以一種特殊的方式處理不同類型的值。 他們會將其左側(cè)的值轉(zhuǎn)換為布爾型,來決定要做什么,但根據(jù)運算符和轉(zhuǎn)換結(jié)果,它們將返回原始的左側(cè)值或右側(cè)值。

例如,當左側(cè)值可以轉(zhuǎn)換為true時,||運算符會返回它,否則返回右側(cè)值。 當值為布爾值時,這具有預期的效果,并且對其他類型的值做類似的操作。

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

我們可以此功能用作回落到默認值的方式。 如果你的一個值可能是空的,你可以把||和備選值放在它之后。 如果初始值可以轉(zhuǎn)換為false,那么你將得到備選值。

&&運算符工作方式與其相似但不相同。當左側(cè)的值可以被轉(zhuǎn)換成false時,&&運算符會返回左側(cè)值,否則返回右側(cè)值。

這兩個運算符的另一個重要特性是,只在必要時求解其右側(cè)的部分。 在true || X的情況下,不管X是什么 - 即使它是一個執(zhí)行某些惡意操作的程序片段,結(jié)果都是true,并且X永遠不會求值。 false && X也是一樣,它是false的,并且忽略X。 這稱為短路求值。

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

本章小結(jié)

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

通過輸入值的名稱(true,null)或值(13"abc")就可以創(chuàng)建它們。你還可以通過運算符來對值進行合并和轉(zhuǎn)換操作。本章已經(jīng)介紹了算術(shù)二元運算符(+,*,/%),字符串連接符(+),比較運算符(==,!====,!==,<>,<=>=),邏輯運算符(&&||)和一些一元運算符(表示負數(shù),!表示邏輯非,typeof用于查詢值的類型)。

這為你提供了足夠的信息,將 JavaScript 用作便攜式計算器,但并不多。 下一章將開始將這些表達式綁定到基本程序中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容