特別說明刀森,為便于查閱焰枢,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS
歡迎來到 你不懂JS(YDKJS)系列。
入門與進階 是一個對幾種編程基本概念的介紹 —— 當(dāng)然我們是特別傾向于JavaScript(經(jīng)常略稱為JS)的 —— 以及如何看待與理解本系列的其他書目。特別是如果你剛剛接觸編程和/或JavaScript炊苫,這本書將簡要地探索你需要什么來 入門與進階裁厅。
這本書從很高的角度來解釋編程的基本原則開始。它基本上假定你是在沒有或很少的編程經(jīng)驗的情況下開始閱讀 YDKJS 的侨艾,而且你期待這些書可以透過JavaScript的鏡頭幫助你開啟一條理解編程的道路执虹。
第一章應(yīng)當(dāng)作為一個快速的概覽來閱讀,它講述為了 進入編程 你將想要多加學(xué)習(xí)和實踐的東西唠梨。有許多其他精彩的編程介紹資源可以幫你在這個話題上走得更遠袋励,而且我鼓勵你學(xué)習(xí)它們來作為這一章的補充。
一旦你對一般的編程基礎(chǔ)感到適應(yīng)了当叭,第二章將指引你熟悉JavaScript風(fēng)格的編程茬故。第二章介紹了JavaScript是什么,但是同樣的蚁鳖,它不是一個全面的指引 —— 那是其他 YDKJS 書目的任務(wù)磺芭!
如果你已經(jīng)相當(dāng)熟悉JavaScript,那么就首先看一下第三章作為 YDKJS 內(nèi)容的簡要一瞥醉箕,然后一頭扎進去吧钾腺!
代碼
讓我們從頭開始。
一個程序讥裤,經(jīng)常被稱為 源代碼 或者只是 代碼放棒,是一組告訴計算機要執(zhí)行什么任務(wù)的特殊指令。代碼通常保存在文本文件中己英,雖然你也可以使用JavaScript在一個瀏覽器的開發(fā)者控制臺中直接鍵入代碼 —— 我們一會兒就會講解间螟。
合法的格式與指令的組合規(guī)則被稱為一種 計算機語言,有時被稱作它的 語法损肛,這和英語教你如何拼寫單詞厢破,和如何使用單詞與標(biāo)點創(chuàng)建合法的句子差不多是相同的。
語句
在一門計算機語言中治拿,一組單詞溉奕,數(shù)字,和執(zhí)行一種具體任務(wù)的操作符構(gòu)成了一個 語句忍啤。在JavaScript中加勤,一個語句可能看起來像下面這樣:
a = b * 2;
字符a
和b
被稱為 變量(參見“變量”),它們就像簡單和盒子同波,你可以把任何東西存儲在其中鳄梅。在程序中,變量持有將被程序使用的值(比如數(shù)字42
)未檩〈魇可以認(rèn)為它們就是值本身的標(biāo)志占位符。
相比之下冤狡,2
本身只是一個值孙蒙,稱為一個 字面值项棠,因為它沒有被存入一個變量,是獨立的挎峦。
字符=
和*
是 操作符(見“操作符”) —— 它們使用值和變量實施動作香追,比如賦值和數(shù)學(xué)乘法。
在JavaScript中大多數(shù)語句都以末尾的分號(;
)結(jié)束坦胶。
語句a = b * 2;
告訴計算機透典,大致上,去取得當(dāng)前存儲在變量b
中的值顿苇,將這個值乘以2
峭咒,然后將結(jié)果存回到另一個我們稱為a
變量里面。
程序只是許多這樣的語句的集合纪岁,它們一起描述為了執(zhí)行你的程序的意圖所要采取的所有步驟凑队。
表達式
語句是由一個或多個 表達式 組成的。一個表達式是一個引用幔翰,指向變量或值漩氨,或者一組用操作符組合的變量和值。
例如:
a = b * 2;
這個語句中有四個表達式:
-
2
是一個 字面量表達式 -
b
是一個 變量表達式导匣,它意味著取出它的當(dāng)前值 -
b * 2
是一個 算數(shù)表達式,它意味著執(zhí)行乘法 -
a = b * 2
是一個 賦值表達式茸时,它意味著將表達式b * 2
的結(jié)果賦值給變量a
(稍后有更多關(guān)于賦值的內(nèi)容)
一個獨立的普通表達式也被稱為一個 表達式語句贡定,比如下面的:
b * 2;
這種風(fēng)格的表達式語句不是很常見也沒什么用,因為一般來說它不會對程序的運行有任何影響 —— 它將取得b
的值并乘以2
可都,但是之后不會對結(jié)果做任何事情缓待。
一種更常見的表達式語句是 調(diào)用表達式 語句(見“函數(shù)”),因為整個語句本身是一個函數(shù)調(diào)用表達式:
alert( a );
執(zhí)行一個程序
這些程序語句的集合如何告訴計算機要做什么渠牲?這個程序需要被 執(zhí)行旋炒,也稱為 運行這個程序。
在開發(fā)者們閱讀與編寫時签杈,像a = b * 2
這樣的語句很有幫助瘫镇,但是它實際上不是計算機可以直接理解的形式。所以一個計算機上的特殊工具(不是一個 解釋器 就是一個 編譯器)被用于將你編寫的代碼翻譯為計算機可以理解的命令答姥。
對于某些計算機語言铣除,這種命令的翻譯經(jīng)常是在每次程序運行時從上向下,一行接一行完成的鹦付,這通常成為代碼的 解釋尚粘。
對于另一些語言,這種翻譯是提前完成的敲长,成為代碼的 編譯郎嫁,所以當(dāng)程序稍后 運行 時秉继,實際上運行的東西已經(jīng)是編譯好,隨時可以運行的計算機指令了泽铛。
JavaScript通常被斷言為是 解釋型 的尚辑,因為你的JavaScript源代碼在它每次運行時都被處理。但這并不是完全準(zhǔn)確的厚宰。JavaScript引擎實際上在即時地 編譯 程序然后立即運行編譯好的代碼腌巾。
注意: 更多關(guān)于JavaScript編譯的信息,參見本系列的 作用域與閉包 的前兩章铲觉。
親自嘗試
這一章將用簡單的代碼段來介紹每一個編程概念澈蝙,它們都是用JavaScript寫的(當(dāng)然!)撵幽。
有一件事情怎么強調(diào)都不過分:在你通讀本章時 —— 而且你可能需要花時間讀好幾遍 —— 你應(yīng)當(dāng)通過自己編寫代碼來實踐這些概念中的每一個灯荧。最簡單的方法就是打開你手邊的瀏覽器(Firefox,Chrome盐杂,IE逗载,等等)的開發(fā)者工具控制臺。
提示: 一般來說链烈,你可以使用快捷鍵或者菜單選項來啟動開發(fā)者控制臺厉斟。更多關(guān)于啟動和使用你最喜歡的瀏覽器的控制臺的細節(jié),參見“精通開發(fā)者工具控制臺”(http://blog.teamtreehouse.com/mastering-developer-tools-console)强衡。要在控制臺中一次鍵入多行擦秽,可以使用<shift> + <enter>
來移動到下一行。一旦你敲擊 <enter>
漩勤,控制臺將運行你剛剛鍵入的任何東西感挥。
讓我們熟悉一下在控制臺中運行代碼的過程。首先越败,我建議你在瀏覽器中打開一個新的標(biāo)簽頁触幼。我喜歡在地址欄中鍵入about:blank
來這么做。然后究飞,確認(rèn)你的開發(fā)者控制臺是打開的置谦,就像我們剛剛提到的那樣。
現(xiàn)在亿傅,鍵入如下代碼看看它是怎么運行的:
a = 21;
b = a * 2;
console.log( b );
在Chrome的控制臺中鍵入前面的代碼應(yīng)該會產(chǎn)生如下的東西:

繼續(xù)霉祸,試試吧。學(xué)習(xí)編程的最佳方式就是開始編碼袱蜡!
輸出
在前一個代碼段中丝蹭,我們使用了console.log(..)
。讓我們簡單地看看這一行代碼在做什么。
你也許已經(jīng)猜到了奔穿,它正是我們?nèi)绾卧陂_發(fā)者控制臺中打印文本(也就是向用戶 輸出)的方法镜沽。這個語句有兩個性質(zhì),我們應(yīng)當(dāng)解釋一下贱田。
首先缅茉,log( b )
部分被稱為一個函數(shù)調(diào)用(見“函數(shù)”)。這里發(fā)生的事情是男摧,我們將變量b
交給這個函數(shù)蔬墩,它向變量b
要來它的值,并在控制臺中打印耗拓。
第二拇颅,console.
部分是一個對象引用,這個對象就是找到log(..)
函數(shù)的地方乔询。我們會在第二章中詳細講解對象和它們的屬性樟插。
另一種創(chuàng)建你可以看到的輸出的方式是運行alert(..)
語句。例如:
alert( b );
如果你運行它竿刁,你會注意到它不會打印輸出到控制臺黄锤,而是顯示一個內(nèi)容為變量b
的“OK”彈出框。但是食拜,一般來說與使用alert(..)
相比鸵熟,使用console.log(..)
會使學(xué)習(xí)編碼和在控制臺運行你的程序更簡單一些,因為你可以一次輸出許多值负甸,而不必干擾瀏覽器的界面流强。
在這本書中,我們將使用console.log(..)
來輸出惑惶。
輸入
雖然我們在討論輸出煮盼,你也許還想知道 輸入(例如短纵,從用戶那里獲得信息)带污。
對于HTML網(wǎng)頁來說,輸入發(fā)生的最常見的方式是向用戶顯示一個他們可以鍵入的form元素香到,然后使用JS將這些值讀入你程序的變量中鱼冀。
但是為了單純的學(xué)習(xí)和展示的目的 —— 也就是你在這本書中將通篇看到的 —— 有一個獲取輸入的更簡單的方法。使用prompt(..)
函數(shù):
age = prompt( "Please tell me your age:" );
console.log( age );
正如你可能已經(jīng)猜到的悠就,你傳遞給prompt(..)
的消息 —— 在這個例子中千绪,"Please tell me your age:"
—— 被打印在彈出框中。
它應(yīng)當(dāng)和下面的東西很相似:

一旦你點擊“OK”提交輸入的文本梗脾,你將會看到你輸入的值被存儲在變量age
中荸型,然后我們使用console.log(..)
把它 輸出:

為了讓我們在學(xué)習(xí)基本編程概念時使事情保持簡單,本書中的例子不要求輸入炸茧。但是現(xiàn)在你已經(jīng)看到了如何使用prompt(..)
瑞妇,如果你想挑戰(zhàn)一下自己稿静,你可以試著在探索這些例子時使用輸入。
操作符
操作符是我們?nèi)绾卧谧兞亢椭瞪蠈嵤┎僮鞯姆绞皆N覀円呀?jīng)見到了兩種JavaScript操作符改备,=
和*
。
*
操作符實施數(shù)學(xué)乘法蔓倍。夠簡單的悬钳,對吧?
=
操作符用于 賦值 —— 我們首先計算=
右手邊 的值(源值)然后將它放進我們在 左手邊 指定的變量中(目標(biāo)變量)偶翅。
警告: 對于指定賦值默勾,這看起來像是一種奇怪的倒置。與a = 42
不同倒堕,一些人喜歡把順序反轉(zhuǎn)過來灾测,于是源值在左而目標(biāo)變量在右,就像42 -> a
(這不是合法的JavaScript?寻汀)媳搪。不幸的是,a = 42
順序的形式骤宣,和與其相似的變種秦爆,在現(xiàn)代編程語言中是十分流行的。如果它讓你覺得不自然憔披,那么就花些時間在腦中演練這個順序并習(xí)慣它等限。
考慮如下代碼:
a = 2;
b = a + 1;
這里,我們將值2
賦值給變量a
芬膝。然后望门,我們?nèi)〉米兞?code>a的值(還是2
),把它加1
得到值3
锰霜,然后將這個值存儲到變量b
中筹误。
雖然在技術(shù)上說var
不是一個操作符,但是你將在每一個程序中都需要這個關(guān)鍵字癣缅,因為它是你 聲明(也就是 創(chuàng)建)變量(見“變量”)的主要方式厨剪。
你應(yīng)當(dāng)總是在使用變量前用名稱聲明它。但是對于每個 作用域(見“作用域”)你只需要聲明變量一次友存;它可以根據(jù)需要使用任意多次祷膳。例如:
var a = 20;
a = a + 1;
a = a * 2;
console.log( a ); // 42
這里是一些在JavaScript中最常見的操作符:
賦值:比如
a = 2
中的=
。數(shù)學(xué):
+
(加法)屡立,-
(減法)直晨,*
(乘法),和/
(除法),比如a * 3
勇皇。復(fù)合賦值:
+=
奕巍,-=
,*=
儒士,和/=
都是復(fù)合操作符的止,它們組合了數(shù)學(xué)操作和賦值,比如a += 2
(與a = a + 2
相同)着撩。遞增/遞減:
++
(遞增)诅福,--
(遞減),比如a++
(和a = a + 1
很相似)拖叙。-
對象屬性訪問:比如
console.log()
的.
氓润。對象是一種值,它可以在被稱為屬性的薯鳍,被具體命名的位置上持有其他的值咖气。
obj.a
意味著一個稱為obj
的對象值有一個名為a
的屬性。屬性可以用obj["a"]
這種替代的方式訪問挖滤。參見第二章崩溪。 -
等價性:
==
(寬松等價),===
(嚴(yán)格等價)斩松,!=
(寬松不等價)伶唯,!==
(嚴(yán)格不等價),比如a == b
惧盹。參見“值與類型”和第二章乳幸。
-
比較:
<
(小于),>
(大于)钧椰,<=
(小于或?qū)捤傻葍r)粹断,>=
(大于或?qū)捤傻葍r),比如a <= b
嫡霞。參見“值與類型”和第二章瓶埋。
-
邏輯:
&&
(與),||
(或)秒际,比如a || b
它選擇a
或b
中的一個悬赏。這些操作符用于表達復(fù)合的條件(見“條件”)狡汉,比如如果
a
或者b
成立娄徊。
注意: 更多細節(jié),以及在此沒有提到的其他操作符盾戴,可以參見Mozilla開發(fā)者網(wǎng)絡(luò)(MDN)的“表達式與操作符”(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators)寄锐。
值與類型
如果你問一個手機店的店員一種特定手機的價格,而他們說“九十九塊九毛九”(即,$99.99)橄仆,他們給了你一個實際的美元數(shù)字來表示你需要花多少錢才能買到它剩膘。如果你想兩部這種手機,你可以很容易地心算這個值的兩倍來得到你需要花費的$199.98盆顾。
如果同一個店員拿起另一部相似的手機說它是“免費的”(也許在用手比劃引號)怠褐,那么他們就不是在給你一個數(shù)字,而是你的花費($0.00)的另一種表達形式 —— “免費”這個詞您宪。
當(dāng)你稍后問到這個手機是否帶充電器時奈懒,回答可能僅僅是“是”或者“不”。
以同樣的方式宪巨,當(dāng)你在程序中表達一個值時磷杏,你根據(jù)你打算對這些值做什么來選擇不同的表達形式。
在編程術(shù)語中值的這些不同的表達形式稱為 類型捏卓。JavaScript中對這些所謂的 基本類型 值都有內(nèi)建的類型:
- 但你需要做數(shù)學(xué)計算時极祸,你需要一個
number
。 - 當(dāng)你需要在屏幕上打印一個值時怠晴,你需要一個
string
(一個或多個字符遥金,單詞,句子)蒜田。 - 當(dāng)你需要在你的程序中做決定時汰规,你需要一個
boolean
(true
或false
)。
在源代碼中直接包含的值稱為 字面量物邑。string
字面量被雙引號"..."
或單引號('...'
)包圍 —— 唯一的區(qū)別是風(fēng)格上的偏好溜哮。number
和boolean
字面量用它們本身來表示(即,42
色解,true
茂嗓,等等)。
考慮如下代碼:
"I am a string";
'I am also a string';
42;
true;
false;
在string
/number
/boolean
值的類型以外科阎,編程語言通常會提供 數(shù)組述吸,對象,函數(shù) 等更多的類型锣笨。我們會在本章和下一章中講解更多關(guān)于值和類型的內(nèi)容蝌矛。
類型間轉(zhuǎn)換
如果你有一個number
但需要將它打印在屏幕上,那么你就需要將這個值轉(zhuǎn)換為一個string
错英,在JavaScript中這種轉(zhuǎn)換稱為“強制轉(zhuǎn)換”入撒。類似地,如果某些人在一個電商網(wǎng)頁的form中輸入一系列數(shù)字椭岩,那么它是一個string
茅逮,但是如果你需要使用這個值去做數(shù)學(xué)運算璃赡,那么你就需要將它 強制轉(zhuǎn)換 為一個number
。
為了在 類型 之間強制轉(zhuǎn)換献雅,JavaScript提供了幾種不同的工具碉考。例如:
var a = "42";
var b = Number( a );
console.log( a ); // "42"
console.log( b ); // 42
使用上面展示的Number(..)
(一個內(nèi)建函數(shù))是一種從任意其他類型到number
類型的 明確的 強制轉(zhuǎn)換。這應(yīng)當(dāng)是相當(dāng)直白的挺身。
但是一個具有爭議的話題是侯谁,當(dāng)你試著比較兩個還不是相同類型的值時發(fā)生的事情,它需要 隱含的 強制轉(zhuǎn)換章钾。
當(dāng)比較字符串"99.99"
和數(shù)字99.99
時良蒸,大多數(shù)人同意它們是等價的。但是他們不完全相同伍玖,不是嗎嫩痰?它們是相同的值的兩種不同表現(xiàn)形式,兩個不同的 類型窍箍。你可以說它們是“寬松地等價”的串纺,不是嗎?
為了在這些常見情況下幫助你椰棘,JavaScript有時會啟動 隱含的 強制轉(zhuǎn)換來把值轉(zhuǎn)換為匹配的類型纺棺。
所以如果你使用==
寬松等價操作符來進行"99.99" == 99.99
比較,JavaScript會將左手邊的"99.99"
轉(zhuǎn)換為它的number
等價物99.99
邪狞。所以比較就變成了99.99 == 99.99
祷蝌,這當(dāng)然是成立的。
雖然隱含強制轉(zhuǎn)換是為了幫助你而設(shè)計帆卓,但是它也可能把你搞糊涂巨朦,如果你沒有花時間去學(xué)習(xí)控制它行為的規(guī)則。大多數(shù)開發(fā)者從沒有這么做剑令,所以常見的感覺是隱含的強制轉(zhuǎn)換是令人困惑的糊啡,并且會產(chǎn)生意外的bug危害程序,因此應(yīng)當(dāng)避免使用吁津。有時它甚至被稱為這種語言中的設(shè)計缺陷棚蓄。
然而,隱含強制轉(zhuǎn)換是一種 可以被學(xué)習(xí) 的機制碍脏,而且是一種 應(yīng)當(dāng) 被所有想要認(rèn)真對待JavaScript編程的人學(xué)習(xí)的機制梭依。一旦你學(xué)習(xí)了這些規(guī)則,它不僅是消除了困惑典尾,而且它實際上是你的程序變得更好役拴!這種努力是值得的。
注意: 關(guān)于強制轉(zhuǎn)換的更多信息急黎,參見本書第二章和本系列 類型與文法 的第四章扎狱。
代碼注釋
手機店店員可能會寫下一些筆記,記下新出的手機的特性或者他們公司推出的新套餐勃教。這些筆記僅僅是給店員使用的 —— 他們不是給顧客讀的淤击。不管怎樣,通過記錄下為什么和如何告訴顧客他應(yīng)當(dāng)說的東西故源,這些筆記幫助店員更好的工作污抬。
關(guān)于編寫代碼你要學(xué)的最重要的課程之一,就是它不僅僅是寫給計算機的绳军。代碼的每一個字節(jié)都和寫給編譯器一樣印机,也是寫給開發(fā)者的。
你的計算機只關(guān)心機器碼门驾,一系列源自 編譯 的0和1射赛。你幾乎可以寫出無限多種可以產(chǎn)生相同0和1序列的代碼。所以你對如何編寫程序作出的決定很重要 —— 不僅是對你奶是,也對你的團隊中的其他成員楣责,甚至是你未來的自己。
你不僅應(yīng)當(dāng)努力去編寫可以正確工作的程序聂沙,而且應(yīng)當(dāng)努力編寫檢視起來有道理的程序秆麸。你可以通過給變量(見“變量”)和函數(shù)(見“函數(shù)”)起一個好名字在這條路上走很遠。
但另外一個重要的部分是代碼注釋及汉。它們純粹是為了向人類解釋一些事情而在你的程序中插入的一點兒文本沮趣。解釋器/編譯器將總是忽略這些注釋。
關(guān)于什么是良好注釋的代碼有許多意見坷随;我們不能真正地定義絕對統(tǒng)一的規(guī)則房铭。但是一些意見和指導(dǎo)是十分有用的:
- 沒有注釋的代碼是次優(yōu)的。
- 過多的注釋(比如温眉,每行都有注釋)可能是代碼編寫的很爛的標(biāo)志育叁。
- 注釋應(yīng)當(dāng)解釋 為什么,而不是 是什么芍殖。它們可以選擇性地解釋 如何做豪嗽,如果代碼特別令人困惑的話。
在JavaScript中豌骏,有兩種可能的注釋類型:單行注釋和多行注釋
考慮如下代碼:
// 這是一個單行注釋
/* 而這是
一個多行
注釋龟梦。
*/
如果你想在一個語句的正上方,或者甚至是在行的末尾加一個注釋窃躲,//
單行注釋是很合適的计贰。這一行上//
之后的所有東西都將被視為注釋(因此被編譯器忽略),一直到行的末尾蒂窒。在單行注釋內(nèi)部可以出現(xiàn)的內(nèi)容沒有限制躁倒。
考慮:
var a = 42; // 生命的意義是 42
如果你想在注釋中用好幾行來解釋一些事情荞怒,/* .. */
多行注釋就很合適。
這是多行注釋的一個常見用法:
/* 使用下面的值是因為
它回答了
全宇宙中所有的問題秧秉。 */
var a = 42;
它還可以出現(xiàn)在一行中的任意位置褐桌,甚至是一行的中間,因為*/
終結(jié)了它象迎。例如:
var a = /* 隨機值 */ 42;
console.log( a ); // 42
在多行注釋中唯一不能出現(xiàn)的就是*/
荧嵌,因為這將干擾注釋的結(jié)尾。
你絕對會希望通過養(yǎng)成注釋代碼的習(xí)慣來開始學(xué)習(xí)編程砾淌。在本書剩余的部分中啦撮,你將看到我使用注釋來解釋事情伤溉,請也在你自己的實踐中這么做敌呈。相信我护戳,所有閱讀你的代碼的人都會感謝你朵诫!
變量
大多數(shù)有用的程序都需要在程序運行整個過程中橘洞,追蹤由于你的程序所意圖的任務(wù)被調(diào)用的底層不同的操作而發(fā)生的值的變化周瞎。
要這樣做的最簡單的方法是將一個值賦予一個符號容器诡宗,稱為一個 變量 —— 因為在這個容器中的值可以根據(jù)需要不時 變化 而得名锹雏。
在某些編程語言中要拂,你可以聲明一個變量(容器)來持有特定類型的值抠璃,比如number
或string
。因為防止了意外的類型轉(zhuǎn)換脱惰,靜態(tài)類型搏嗡,也被稱為 類型強制,通常被認(rèn)為是對程序正確性有好處的拉一。
另一些語言在值上強調(diào)類型而非在變量上采盒。弱類型,也被稱為 動態(tài)類型蔚润,允許變量在任意時刻持有任意類型的值磅氨。因為它允許一個變量在程序邏輯流程中代表一個值,而不論這個值在任意給定的時刻是什么類型嫡纠,所以它被認(rèn)為是對程序靈活性有好處的烦租。
JavaScript使用的是后者,動態(tài)類型除盏,這意味著變量可以持有任意 類型 的值而沒有任何 類型 強制約束叉橱。
正如我們剛才提到的,我們使用var
語句來聲明一個變量 —— 注意在這種聲明中沒有其他的 類型 信息者蠕∏宰#考慮這段簡單的代碼:
var amount = 99.99;
amount = amount * 2;
console.log( amount ); // 199.98
// 將 `amount` 轉(zhuǎn)換為一個字符串,
// 并在開頭加一個 "$"
amount = "$" + String( amount );
console.log( amount ); // "$199.98"
變量amount
開始時持有數(shù)字99.99
踱侣,然后持有amount * 2
的number
結(jié)果粪小,也就是199.98
大磺。
第一個console.log(..)
命令不得不 隱含地 將這個number
值強制轉(zhuǎn)換為一個string
才能夠打印出來。
然后語句amount = "$" + String(amount)
明確地 將值199.98
強制轉(zhuǎn)換為一個string
并且在開頭加入一個"$"
字符探膊。這時杠愧,amount
現(xiàn)在就持有這個string
值$199.98
,所以第二個console.log(..)
語句無需強制轉(zhuǎn)換就可以把它打印出來突想。
JavaScript開發(fā)者將會注意到為值99.99
殴蹄,199.98
究抓,和"$199.98"
都使用變量amount
的靈活性猾担。靜態(tài)類型的擁護者們將偏好于使用一個分離的變量,比如amountStr
來持有這個值最后的"$199.98"
表達形式刺下,因為它是一個不同的類型绑嘹。
不管哪種方式,你將會注意到amount
持有一個在程序運行過程中不斷變化的值橘茉,這展示了變量的主要目地:管理程序 狀態(tài)工腋。
換句話說,在你程序運行的過程中 狀態(tài) 追蹤著值的改變畅卓。
變量的另一種常見用法是將值的設(shè)定集中化擅腰。當(dāng)你為一個在程序中通篇不打算改變的值聲明了一個變量時,它更一般地被稱為 常量翁潘。
你經(jīng)常會在程序的頂部聲明這些 常量趁冈,這樣提供了一種方便:如果你需要改變一個值時你可以到唯一的地方去尋找。根據(jù)慣例拜马,用做常量的JavaScript變量通常是大寫的渗勘,在多個單詞之間使用下劃線_
連接。
這里是一個呆萌的例子:
var TAX_RATE = 0.08; // 8% sales tax
var amount = 99.99;
amount = amount * 2;
amount = amount + (amount * TAX_RATE);
console.log( amount ); // 215.9784
console.log( amount.toFixed( 2 ) ); // "215.98"
注意: console.log(..)
是一個函數(shù)log(..)
作為一個在值console
上的對象屬性被訪問俩莽,與此類似旺坠,這里的toFixed(..)
是一個可以在值number
上被訪問的函數(shù)。JavaScript number
不會被自動地格式化為美元 —— 引擎不知道你的意圖扮超,而且也沒有通貨類型取刃。toFixed(..)
讓我們指明四舍五入到小數(shù)點后多少位,而且它如我們需要的那樣產(chǎn)生一個string
出刷。
變量TAX_RATE
只是因為慣例才是一個 常量 —— 在這個程序中沒有什么特殊的東西可以防止它被改變蝉衣。但是如果這座城市將它的消費稅增至9%,我們?nèi)匀豢梢院苋莸赝ㄟ^在一個地方將TAX_RATE
被賦予的值改為0.09
來更新我們的程序巷蚪,而不是在程序通篇中尋找許多值0.08
出現(xiàn)的地方然后更新它們?nèi)俊?/p>
在寫作本書時病毡,最新版本的JavaScript(通常稱為“ES6”)引入了一個聲明常量的新方法,用const
代替var
:
// 在ES6中:
const TAX_RATE = 0.08;
var amount = 99.99;
// ..
常量就像帶有不變的值的變量一樣有用屁柏,常量還防止在初始設(shè)置之后的某些地方意外地改變它的值啦膜。如果你試著在第一個聲明之后給TAX_RATE
賦予一個不同的值有送,你的程序?qū)芙^這個改變(而且在Strict模式下,會產(chǎn)生一個錯誤 —— 見第二章的“Strict模式”)僧家。
順帶一提雀摘,這種防止編程錯誤的“保護”與靜態(tài)類型的類型強制很類似,所以你可以看到為什么在其他語言中的靜態(tài)類型很吸引人八拱。
注意: 更多關(guān)于如何在你程序的變量中使用不同的值阵赠,參見本系列的 類型與文法。
塊兒
在你買你的新手機時肌稻,手機店店員必須走過一系列步驟才能完成結(jié)算清蚀。
相似地,在代碼中我們經(jīng)常需要將一系列語句一起分為一組爹谭,這就是我們常說的 塊兒枷邪。在JavaScript中,一個塊兒被定義為包圍在一個大括號{ .. }
中的一個或多個語句诺凡《В考慮如下代碼:
var amount = 99.99;
// 一個普通的塊兒
{
amount = amount * 2;
console.log( amount ); // 199.98
}
這種獨立的{ .. }
塊兒是合法的,但是在JS程序中并不常見腹泌。一般來說嘶卧,塊兒是添附在一些其他的控制語句后面的,比如一個if
語句(見“條件”)或者一個循環(huán)(見“循環(huán)”)凉袱。例如:
var amount = 99.99;
// 數(shù)值夠大嗎芥吟?
if (amount > 10) { // <-- 添附在`if`上的塊兒
amount = amount * 2;
console.log( amount ); // 199.98
}
我們將在下一節(jié)講解if
語句,但是如你所見绑蔫,{ .. }
塊兒帶著它的兩個語句被添附在if (amount > 10)
后面运沦;塊兒中的語句將會僅在條件成立時被處理。
注意: 與其他大多數(shù)語句不同(比如console.log(amount);
)配深,一個塊兒語句不需要分號(;
)來終結(jié)它携添。
條件
“你想來一個額外的屏幕貼膜嗎?只要$9.99篓叶×衣樱” 熱心的手機店店員請你做個決定。而你也許需要首先咨詢一下錢包或銀行帳號的 狀態(tài) 才能回答這個問題缸托。但很明顯左敌,這只是一個簡單的“是與否”的問題。
在我們的程序中有好幾種方式可以表達 條件(也就是決定)俐镐。
最常見的一個就是if
語句矫限。實質(zhì)上,你在說,“如果 這個條件成立叼风,做后面的……”取董。例如:
var bank_balance = 302.13;
var amount = 99.99;
if (amount < bank_balance) {
console.log( "I want to buy this phone!" );
}
if
語句在括號( )
之間需要一個表達式,它不是被視作true
就是被視作false
无宿。在這個程序中茵汰,我們提供了表達式amount < bank_balance
,它確實會根據(jù)變量bank_balance
中的值被求值為true
或false
孽鸡。
如果條件不成立蹂午,你甚至可以提供一個另外的選擇,稱為else
子句彬碱《剐兀考慮下面的代碼:
const ACCESSORY_PRICE = 9.99;
var bank_balance = 302.13;
var amount = 99.99;
amount = amount * 2;
// 我們買得起配件嗎?
if ( amount < bank_balance ) {
console.log( "I'll take the accessory!" );
amount = amount + ACCESSORY_PRICE;
}
// 否則:
else {
console.log( "No, thanks." );
}
在這里堡妒,如果amount < bank_balance
是true
配乱,我們將打印出"I'll take the accessory!"
并在我們的變量amount
上加9.99
溉卓。否則皮迟,else
子句說我們將禮貌地回應(yīng)"No, thanks."
,并保持amount
不變桑寨。
正如我們在早先的“值與類型”中討論的伏尼,一個還不是所期望類型的值經(jīng)常會被強制轉(zhuǎn)換為那種類型。if
語句期待一個boolean
尉尾,但如果你傳給它某些還不是boolean
的東西爆阶,強制轉(zhuǎn)換就會發(fā)生。
JavaScript定義了一組特定的被認(rèn)為是“falsy”的值沙咏,因為在強制轉(zhuǎn)換為boolean
時辨图,它們將變?yōu)?code>false —— 這些值包括0
和""
。任何不再這個falsy
列表中的值都自動是“truthy” —— 當(dāng)強制轉(zhuǎn)換為boolean
時它們變?yōu)?code>true肢藐。truthy值包括99.99
和"free"
這樣的東西故河。更多信息參見第二章的“Truthy與Falsy”。
除了if
條件 還以其他形式存在吆豹。例如鱼的,switch
語句可以被用作一系列if..else
語句的縮寫(見第二章)。循環(huán)(見“循環(huán)”)使用一個 條件 來決定循環(huán)是否應(yīng)當(dāng)繼續(xù)或停止痘煤。
注意: 關(guān)于在 條件 的測試表達式中可能發(fā)生的隱含強制轉(zhuǎn)換的更深層的信息凑阶,參見本系列的 類型與文法 的第四章。
循環(huán)
在繁忙的時候衷快,有一張排隊單宙橱,上面記載著需要和手機店店員談話的顧客。雖然排隊單上還有許多人,但是她只需要持續(xù)服務(wù)下一位顧客就好了师郑。
重復(fù)一組動作直到特定的條件失敗 —— 換句話說哼勇,僅在條件成立時重復(fù) —— 就是程序循環(huán)的工作;循環(huán)可以有不同的形式呕乎,但是它們都符合這種基本行為积担。
一個循環(huán)包含測試條件和一個塊兒(通常是{ .. }
)。每次循環(huán)塊兒執(zhí)行猬仁,都稱為一次 迭代帝璧。
例如,while
循環(huán)和do..while
循環(huán)形式就說明了這種概念 —— 重復(fù)一塊兒語句直到一個條件不再求值得true
:
while (numOfCustomers > 0) {
console.log( "How may I help you?" );
// 服務(wù)顧客……
numOfCustomers = numOfCustomers - 1;
}
// 與
do {
console.log( "How may I help you?" );
// 服務(wù)顧客……
numOfCustomers = numOfCustomers - 1;
} while (numOfCustomers > 0);
這些循環(huán)之間唯一的實際區(qū)別是湿刽,條件是在第一次迭代之前(while
)還是之后(do..while
)被測試的烁。
在這兩種形式中,如果條件測試得false
诈闺,那么下一次迭代就不會運行渴庆。這意味著如果條件初始時就是false
,那么while
循環(huán)就永遠不會運行雅镊,但是一個do..while
循環(huán)將僅運行一次襟雷。
有時你會為了計數(shù)一組特定的數(shù)字來進行循環(huán),比如從0
到9
(十個數(shù))仁烹。你可以通過設(shè)定一個值為0
的循環(huán)迭代變量耸弄,比如i
,并在每次迭代時將它遞增1
卓缰。
警告: 由于種種歷史原因计呈,編程語言幾乎總是用從零開始的方式來計數(shù)的,這意味著計數(shù)開始于0
而不是1
征唬。如果你不熟悉這種思維模式捌显,一開始它可能十分令人困惑。為了更適應(yīng)它总寒,花些時間練習(xí)從0
開始數(shù)數(shù)吧扶歪!
條件在每次迭代時都會被測試,好像在循環(huán)內(nèi)部有一個隱含的if
語句一樣偿乖。
你可以使用JavaScript的break
語句來停止一個循環(huán)击罪。另外,我們可以看到如果沒有break
機制贪薪,就會極其容易地創(chuàng)造一個永遠運行的循環(huán)媳禁。
讓我們展示一下:
var i = 0;
// 一個 `while..true` 循環(huán)將會永遠運行,對吧画切?
while (true) {
// 停止循環(huán)竣稽?
if ((i <= 9) === false) {
break;
}
console.log( i );
i = i + 1;
}
// 0 1 2 3 4 5 6 7 8 9
警告: 這未必是你想在你的循環(huán)中使用的實際形式。它是僅為了說明的目的才出現(xiàn)在這里的。
雖然一個while
(或do..while
)可以手動完成任務(wù)毫别,但是為了同樣的目的娃弓,還有一種稱為for
循環(huán)的語法形式:
for (var i = 0; i <= 9; i = i + 1) {
console.log( i );
}
// 0 1 2 3 4 5 6 7 8 9
如你所見,對于這兩種循環(huán)形式來說岛宦,前10次迭代(i
的值從0
到9
)的條件i <= 9
都是true
台丛,而且一旦i
值為10
就變?yōu)?code>false。
for
循環(huán)有三個子句:初始化子句(var i=0
)砾肺,條件測試子句(i <= 9
)挽霉,和更新子句(i = i + 1
)。所以如果你想要使用循環(huán)迭代來計數(shù)变汪,for
是一個更緊湊而且更易理解和編寫的形式侠坎。
還有一些意在迭代特定的值的特殊循環(huán)形式,比如迭代一個對象的屬性(見第二章)裙盾,它隱含的測試條件是所有的屬性是否都被處理過了实胸。無論循環(huán)是何種形式,“循環(huán)直到條件失敗”的概念是它們共有的番官。
函數(shù)
手機店的店員可能不會拿著一個計算器到處走庐完,用它來搞清稅費和最終的購物款。這是一個她需要定義一次然后一遍又一遍地重用的任務(wù)鲤拿。很有可能的是假褪,公司有一個帶有內(nèi)建這些“功能”的收銀機(電腦署咽,平板電腦近顷,等等)。
相似地宁否,幾乎可以肯定你的程序想要將代碼的任務(wù)分割成可以重用的片段窒升,而不是頻繁地多次重復(fù)自己。這么做的方法是定義一個function
慕匠。
一個函數(shù)一般來說是一段被命名的代碼饱须,它可以使用名稱來被“調(diào)用”,而每次調(diào)用它內(nèi)部的代碼就會運行台谊∪叵保考慮如下代碼:
function printAmount() {
console.log( amount.toFixed( 2 ) );
}
var amount = 99.99;
printAmount(); // "99.99"
amount = amount * 2;
printAmount(); // "199.98"
函數(shù)可以選擇性地接收參數(shù)值(也就是參數(shù))—— 你傳入的值。而且它們還可以選擇性地返回一個值锅铅。
function printAmount(amt) {
console.log( amt.toFixed( 2 ) );
}
function formatAmount() {
return "$" + amount.toFixed( 2 );
}
var amount = 99.99;
printAmount( amount * 2 ); // "199.98"
amount = formatAmount();
console.log( amount ); // "$99.99"
函數(shù)printAmount(..)
接收一個參數(shù)酪呻,我們稱之為amt
。函數(shù)formatAmount()
返回一個值盐须。當(dāng)然玩荠,你也可以在同一個函數(shù)中組合這兩種技術(shù)。
函數(shù)經(jīng)常被用于你打算多次調(diào)用的代碼,但它們對于僅將有關(guān)聯(lián)的代碼組織在一個命名的集合中也很有用阶冈,即便你只打算調(diào)用它們一次闷尿。
考慮如下代碼:
const TAX_RATE = 0.08;
function calculateFinalPurchaseAmount(amt) {
// 計算帶有稅費的新費用
amt = amt + (amt * TAX_RATE);
// 返回新費用
return amt;
}
var amount = 99.99;
amount = calculateFinalPurchaseAmount( amount );
console.log( amount.toFixed( 2 ) ); // "107.99"
雖然calculateFinalPurchaseAmount(..)
只被調(diào)用了一次,但是將它的行為組織進一個分離的帶名稱的函數(shù)女坑,讓使用它邏輯的代碼(amount = calculateFinal...
語句)更干凈填具。如果函數(shù)中擁有更多的語句,這種好處將會更加明顯匆骗。
作用域
如果你向手機店的店員詢問一款她們店里沒有的手機灌旧,那么她就不能賣給你你想要的。她只能訪問她們店庫房里的手機绰筛。你不得不到另外一家店里去看看能不能找到你想要的手機枢泰。
編程對這種概念有一個術(shù)語:作用域(技術(shù)上講稱為 詞法作用域)。在JavaScript中铝噩,每個函數(shù)都有自己的作用域衡蚂。作用域基本上就是變量的集合,也是如何使用名稱訪問這些變量的規(guī)則骏庸。只有在這個函數(shù)內(nèi)部的代碼才能訪問這個函數(shù) 作用域內(nèi) 的變量毛甲。
在同一個作用域內(nèi)變量名必須是唯一的 —— 不能有兩個不同的變量a
并排出現(xiàn)。但是相同的變量名a
可以出現(xiàn)在不同的作用域中具被。
function one() {
// 這個 `a` 僅屬于函數(shù) `one()`
var a = 1;
console.log( a );
}
function two() {
// 這個 `a` 僅屬于函數(shù) `two()`
var a = 2;
console.log( a );
}
one(); // 1
two(); // 2
另外玻募,一個作用域可以嵌套在另一個作用域中,就像生日Party上的小丑在一個氣球的里面吹另一個氣球一樣一姿。如果一個作用域嵌套在另一個中七咧,那么在內(nèi)部作用域中的代碼就可以訪問這兩個作用域中的變量。
考慮如下代碼:
function outer() {
var a = 1;
function inner() {
var b = 2;
// 我們可以在這里同時訪問 `a` 和 `b`
console.log( a + b ); // 3
}
inner();
// 我們在這里只能訪問 `a`
console.log( a ); // 1
}
outer();
詞法作用域規(guī)則說叮叹,在一個作用域中的代碼既可以訪問這個作用域中的變量艾栋,又可以訪問任何在它外面的作用域的變量。
所以蛉顽,在函數(shù)inner()
內(nèi)部的代碼可以同時訪問變量a
和b
蝗砾,但是僅在outer()
中的代碼只能訪問a
—— 它不能訪問b
因為這個變量僅存在于inner()
內(nèi)部。
回憶一下先前的這個代碼段:
const TAX_RATE = 0.08;
function calculateFinalPurchaseAmount(amt) {
// 計算帶有稅費的新費用
amt = amt + (amt * TAX_RATE);
// 返回新費用
return amt;
}
因為詞法作用域携冤,常數(shù)TAX_RATE
(變量)可以從calculateFinalPurchaseAmount(..)
函數(shù)中訪問悼粮,即便它沒有被傳入這個函數(shù)。
注意: 關(guān)于詞法作用域的更多信息曾棕,參見本系列的 作用域與閉包 的前三章扣猫。
練習(xí)
在編程的學(xué)習(xí)中絕對沒有什么可以替代練習(xí)。我寫的再好也不可能使你成為一個程序員睁蕾。
帶著這樣的意識苞笨,讓我們試著練習(xí)一下我們在本章學(xué)到的一些概念债朵。我將給出“需求”,而你首先試著實現(xiàn)它瀑凝。然后參考下面的代碼清單來看看我是怎么處理它的序芦。
- 寫一個程序來計算你購買手機的總價。你將不停地購買手機直到你的銀行賬戶上的錢都用光(提示:循環(huán)T吝洹)谚中。你還將為每個手機購買配件,只要你的花費低于你心理預(yù)算寥枝。
- 在你計算完購買總價之后宪塔,加入稅費,然后用合適的格式打印出計算好的購買總價囊拜。
- 最后某筐,將總價與你銀行賬戶上的余額作比較,來看看那你是否買的起冠跷。
- 你應(yīng)當(dāng)為“稅率”南誊,“手機價格”,“配件價格”和“花費預(yù)算”設(shè)置一些常數(shù)蜜托,也為你的“銀行賬戶余額”設(shè)置一個變量抄囚。
- 你應(yīng)當(dāng)為稅費的計算和價格的格式化 —— 使用一個“$”并四舍五入到小數(shù)點后兩位 —— 定義函數(shù)。
-
加分挑戰(zhàn): 試著在這個程序中利用輸入橄务,也許是使用在前面的“輸入”中講過的
prompt(..)
幔托。比如,你可能會提示用戶輸入它們的銀行賬戶余額蜂挪。發(fā)揮創(chuàng)造力好好玩兒吧重挑!
好的,去吧锅劝。試試看攒驰。在你自己實踐過之前不要偷看我的代碼清單!
注意: 因為這是一本JavaScript書故爵,很明顯我將使用JavaScript解決這個聯(lián)系。但是目前你可使用其他的語言隅津,如果你感覺更適應(yīng)的話诬垂。
對于這個練習(xí),這是我的JavaScript解決方案:
const SPENDING_THRESHOLD = 200;
const TAX_RATE = 0.08;
const PHONE_PRICE = 99.99;
const ACCESSORY_PRICE = 9.99;
var bank_balance = 303.91;
var amount = 0;
function calculateTax(amount) {
return amount * TAX_RATE;
}
function formatAmount(amount) {
return "$" + amount.toFixed( 2 );
}
// 只要你還有錢就不停地買手機
while (amount < bank_balance) {
// 買個新手機
amount = amount + PHONE_PRICE;
// 還買得起配件嗎伦仍?
if (amount < SPENDING_THRESHOLD) {
amount = amount + ACCESSORY_PRICE;
}
}
// 也別忘了給政府交錢
amount = amount + calculateTax( amount );
console.log(
"Your purchase: " + formatAmount( amount )
);
// Your purchase: $334.76
// 你買的起嗎结窘?
if (amount > bank_balance) {
console.log(
"You can't afford this purchase. :("
);
}
// 你買不起 :(
注意: 運行這個JavaScript程序的最簡單的方法是將它鍵入到你手邊的瀏覽器的開發(fā)者控制臺中。
你做的怎么樣充蓝?看了我的代碼之后隧枫,現(xiàn)在再試一次也沒什么不好喉磁。而且你可以改變某些常數(shù)來看看使用不同的值時這個程序運行的如何。
復(fù)習(xí)
學(xué)習(xí)編程不一定是個復(fù)雜而且巨大的過程官脓。你只需要在腦中裝進幾個基本的概念协怒。
它們就像構(gòu)建塊兒。要建一座高塔卑笨,你就要從堆砌構(gòu)建塊兒開始孕暇。編程也一樣。這里是一些編程中必不可少的構(gòu)建塊兒:
- 你需要 操作符 來在值上實施動作赤兴。
- 你需要值和 類型 來試試不同種類的動作妖滔,比如在
number
上做數(shù)學(xué),或者使用string
輸出桶良。 - 你需要 變量 在你程序執(zhí)行的過程中存儲數(shù)據(jù)(也就是 狀態(tài))座舍。
- 你需要 條件,比如
if
語句來做決定陨帆。 - 你需要 循環(huán) 來重復(fù)任務(wù)簸州,直到一個條件不再成立。
- 你需要 函數(shù) 來將你的代碼組織為有邏輯的和可復(fù)用的塊兒歧譬。
代碼注釋是一種編寫更好可讀性代碼的有效方法岸浑,它使你的代碼更易理解,維護瑰步,而且如果稍后出現(xiàn)問題的話更易修改矢洲。
最后,不要忽視練習(xí)的力量缩焦。學(xué)習(xí)寫代碼的最好方法就是寫代碼读虏。
現(xiàn)在,我很高興看到你在學(xué)習(xí)編碼的道路上走得很好袁滥!保持下去盖桥。不要忘了看看其他編程初學(xué)者的資源(書,博客题翻,在線教學(xué)揩徊,等等)。這一章和這本書是一個很好的開始嵌赠,但它們只是一個簡要的介紹塑荒。
下一章將會復(fù)習(xí)許多本章中的概念,但是是從更加專門于JavaScript的視角姜挺,這將突出將在本系列的剩余部分將要深度剖析的大多數(shù)主要話題齿税。