來源: ApacheCN『JavaScript 編程精解 中文第三版』翻譯項目原文:Program Structurejavascript
譯者:飛龍html
協議:CC BY-NC-SA 4.0java
自豪地採用谷歌翻譯git
部分參考了《JavaScript 編程精解(第 2 版)》程序員
And my heart glows bright red under my filmy, translucent skin and they have to administer 10cc of JavaScript to get me to come back. (I respond well to toxins in the blood.) Man, that stuff will kick the peaches right out your gills!github
why,《Why's (Poignant) Guide to Ruby》面試
在本章中,咱們開始作一些實際上稱爲編程的事情。 咱們將擴展咱們對 JavaScript 語言的掌控,超出咱們目前所看到的名詞和句子片段,直到咱們能夠表達有意義的散文。apache
在第 1 章中,咱們爲它們建立了值,並應用了運算符來得到新的值。 像這樣建立值是任何 JavaScript 程序的主要內容。 可是,這種東西必須在更大的結構中構建,才能發揮做用。 這就是咱們接下來要作的。編程
咱們把產生值的操做的代碼片斷稱爲表達式。按照字面含義編寫的值(好比22
或"psychoanalysis"
)都是一個表達式。而括號當中的表達式、使用二元運算符鏈接的表達式或使用一元運算符的表達式,仍然都是表達式。瀏覽器
這展現了一部分基於語言的接口之美。 表達式能夠包含其餘表達式,其方式很是相似於人類語言的從句嵌套 - 從句能夠包含它本身的從句,依此類推。 這容許咱們構建描述任意複雜計算的表達式。
若是一個表達式對應一個句子片斷,則 JavaScript 語句對應於一個完整的句子。 一個程序是一列語句。
最簡單的一條語句由一個表達式和其後的分號組成。好比這就是一個程序:
1; !false;
不過,這是一個無用的程序。 表達式能夠僅僅知足於產生一個值,而後能夠由閉合的代碼使用。 一個聲明是獨立存在的,因此它只有在影響到世界的時候纔會成立。 它能夠在屏幕上顯示某些東西 - 這能夠改變世界 - 或者它能夠改變機器的內部狀態,從而影響後面的語句。 這些變化被稱爲反作用。 前面例子中的語句僅僅產生值1
和true
,而後當即將它們扔掉。 這給世界沒有留下什麼印象。 當你運行這個程序時,什麼都不會發生。
在某些狀況下,JavaScript 容許您在語句結尾處省略分號。 在其餘狀況下,它必須在那裏,不然下一行將被視爲同一語句的一部分。 什麼時候能夠安全省略它的規則有點複雜且容易出錯。 因此在本書中,每個須要分號的語句都會有分號。 至少在你更瞭解省略分號的細節以前,我建議你也這樣作。
程序如何保持內部狀態? 它如何記住東西? 咱們已經看到如何從舊值中產生新值,但這並無改變舊值,新值必須當即使用,不然將會再度消失。 爲了捕獲和保存值,JavaScript 提供了一種稱爲綁定或變量的東西:
let caught = 5 * 5;
這是第二種語句。 關鍵字(keyword)let
表示這個句子打算定義一個綁定。 它後面跟着綁定的名稱,若是咱們想當即給它一個值,使用=
運算符和一個表達式。
前面的語句建立一個名爲caught
的綁定,並用它來捕獲乘以5 * 5
所產生的數字。
在定義綁定以後,它的名稱能夠用做表達式。 這種表達式的值是綁定當前所持有的值。 這是一個例子:
let ten = 10; console.log(ten * ten); // → 100
當綁定指向某個值時,並不意味着它永遠與該值綁定。 能夠在現有的綁定上隨時使用=
運算符,將它們與當前值斷開鏈接,並讓它們指向一個新值:
var mood = "light"; console.log(mood); // → light mood = "dark"; console.log(mood); // → dark
你應該將綁定想象爲觸手,而不是盒子。 他們不包含值; 他們捕獲值 - 兩個綁定能夠引用相同的值。 程序只能訪問它還在引用的值。 當你須要記住某些東西時,你須要長出一個觸手來捕獲它,或者你從新貼上你現有的觸手之一。
咱們來看另外一個例子。 爲了記住 Luigi 欠你的美圓數量,你須要建立一個綁定。 而後當他還你 35 美圓時,你賦予這個綁定一個新值:
let luigisDebt = 140; luigisDebt = luigisDebt - 35; console.log(luigisDebt); // → 105
當你定義一個綁定而沒有給它一個值時,觸手沒有任何東西能夠捕獲,因此它只能捕獲空氣。 若是你請求一個空綁定的值,你會獲得undefined
值。
一個let
語句能夠同時定義多個綁定,定義必需用逗號分隔。
let one = 1, two = 2; console.log(one + two); // → 3
var
和const
這兩個詞也能夠用來建立綁定,相似於let
。
var name = "Ayda"; const greeting = "Hello "; console.log(greeting + name); // → Hello Ayda
第一個var
(「variable」的簡寫)是 JavaScript 2015 以前聲明綁定的方式。 咱們在下一章中,會講到它與let
的確切的不一樣之處。 如今,請記住它大部分都作一樣的事情,但咱們不多在本書中使用它,由於它有一些使人困惑的特性。
const
這個詞表明常量。 它定義了一個不變的綁定,只要它存在,它就指向相同的值。 這對於一些綁定頗有用,它們向值提供一個名詞,以便以後能夠很容易地引用它。
綁定名稱能夠是任何單詞。 數字能夠是綁定名稱的一部分,例如catch22
是一個有效的名稱,但名稱不能以數字開頭。 綁定名稱可能包含美圓符號($
)或下劃線(_
),但不包含其餘標點符號或特殊字符。
具備特殊含義的詞,如let
,是關鍵字,它們不能用做綁定名稱。 在將來的 JavaScript 版本中還有一些「保留供使用」的單詞,它們也不能用做綁定名稱。 關鍵字和保留字的完整列表至關長:
break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import interface in instanceof let new package private protected public return static super switch this throw true try typeof var void while with yield
不要擔憂記住這些東西。 建立綁定時會產生意外的語法錯誤,請查看您是否嘗試定義保留字。
給定時間中存在的綁定及其值的集合稱爲環境。 當一個程序啓動時,這個環境不是空的。 它老是包含做爲語言標準一部分的綁定,而且在大多數狀況下,它還具備一些綁定,提供與周圍系統交互的方式。 例如,在瀏覽器中,有一些功函數能能夠與當前加載的網站交互並讀取鼠標和鍵盤輸入。
在默認環境中提供的許多值的類型爲函數。 函數是包裹在值中的程序片斷。 爲了運行包裹的程序,能夠將這些值應用於它們。 例如,在瀏覽器環境中,綁定prompt
包含一函數,個顯示一個小對話框,請求用戶輸入。 它是這樣使用的:
prompt("Enter passcode");
執行一個函數被稱爲調用,或應用它(invoke,call,apply)。您能夠經過在生成函數值的表達式以後放置括號來調用函數。 一般你會直接使用持有該函數的綁定名稱。 括號之間的值被賦予函數內部的程序。 在這個例子中,prompt
函數使用咱們提供的字符串做爲文原本顯示在對話框中。 賦予函數的值稱爲參數。 不一樣的函數可能須要不一樣的數量或不一樣類型的參數。
prompt
函數在現代 Web 編程中用處不大,主要是由於你沒法控制所得對話框的外觀,但能夠在玩具程序和實驗中有所幫助。
console.log
函數在例子中,我使用console.log
來輸出值。 大多數 JavaScript 系統(包括全部現代 Web 瀏覽器和 Node.js)都提供了console.log
函數,將其參數寫入一個文本輸出設備。 在瀏覽器中,輸出出如今 JavaScript 控制檯中。 瀏覽器界面的這一部分在默認狀況下是隱藏的,但大多數瀏覽器在您按 F12 或在 Mac 上按 Command-Option-I 時打開它。 若是這不起做用,請在菜單中搜索名爲「開發人員工具」或相似的項目。
在英文版頁面上運行示例(或本身的代碼)時,會在示例以後顯示
console.log
輸出,而不是在瀏覽器的 JavaScript 控制檯中顯示。
let x = 30; console.log("the value of x is", x); // → the value of x is 30
儘管綁定名稱不能包含句號字符,可是console.log
確實擁有。 這是由於console.log
不是一個簡單的綁定。 它其實是一個表達式,它從console
綁定所持有的值中檢索log
屬性。 咱們將在第 4 章中弄清楚這意味着什麼。
顯示對話框或將文字寫入屏幕是一個反作用。 因爲它們產生的反作用,不少函數都頗有用。 函數也可能產生值,在這種狀況下,他們不須要有反作用就有用。 例如,函數Math.max
能夠接受任意數量的參數並返回最大值。
console.log(Math.max(2, 4)); // → 4
當一個函數產生一個值時,它被稱爲返回該值。 任何產生值的東西都是 JavaScript 中的表達式,這意味着能夠在較大的表達式中使用函數調用。 在這裏,Math.min
的調用(與Math.max
相反)用做加法表達式的一部分:
console.log(Math.min(2, 4) + 100); // → 102
咱們會在下一章當中講解如何編寫自定義函數。
當你的程序包含多個語句時,這些語句就像是一個故事同樣從上到下執行。 這個示例程序有兩個語句。 第一個要求用戶輸入一個數字,第二個在第一個以後執行,顯示該數字的平方。
let theNumber = Number(prompt("Pick a number")); console.log("Your number is the square root of " + theNumber * theNumber);
Number
函數將一個值轉換爲一個數字。 咱們須要這種轉換,由於prompt
的結果是一個字符串值,咱們須要一個數字。 有相似的函數叫作String
和Boolean
,它們將值轉換爲這些類型。
如下是直線控制流程的至關簡單的示意圖:
並不是全部的程序都是直路。 例如,咱們可能想建立一條分叉路,在那裏該程序根據當前的狀況採起適當的分支。 這被稱爲條件執行。
在 JavaScript 中,條件執行使用if
關鍵字建立。 在簡單的狀況下,當且僅當某些條件成立時,咱們才但願執行一些代碼。 例如,僅當輸入其實是一個數字時,咱們可能打算顯示輸入的平方。
let theNumber = Number(prompt("Pick a number", "")); if (!isNaN(theNumber)) alert("Your number is the square root of " + theNumber * theNumber);
修改以後,若是您輸入"parrot"
,則不顯示輸出。
if
關鍵字根據布爾表達式的值執行或跳過語句。 決定性的表達式寫在關鍵字以後,括號之間,而後是要執行的語句。
Number.isNaN
函數是一個標準的 JavaScript 函數,僅當它給出的參數是NaN
時才返回true
。 當你給它一個不表明有效數字的字符串時,Number
函數剛好返回NaN
。 所以,條件翻譯爲「若是theNumber
是一個數字,那麼這樣作」。
在這個例子中,if
下面的語句被大括號({
和}
)括起來。 它們可用於將任意數量的語句分組到單個語句中,稱爲代碼塊。 在這種狀況下,你也能夠忽略它們,由於它們只包含一個語句,但爲了不必須考慮是否須要,大多數 JavaScript 程 序員在每一個這樣的被包裹的語句中使用它們。 除了偶爾的一行,咱們在本書中大多會遵循這個約定。
if (1 + 1 == 2) console.log("It's true"); // → It's true
您一般不會只執行條件成立時代碼,還會處理其餘狀況的代碼。 該替代路徑由圖中的第二個箭頭表示。 能夠一塊兒使用if
和else
關鍵字,建立兩個單獨的替代執行路徑。
let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); } else { console.log("Hey. Why didn't you give me a number?"); }
若是咱們須要執行的路徑多於兩條,能夠將多個if/else
對連接在一塊兒使用。以下所示例子:
let num = Number(prompt("Pick a number", "0")); if (num < 10) { console.log("Small"); } else if (num < 100) { console.log("Medium"); } else { console.log("Large"); }
該程序首先會檢查num
是否小於 10。若是條件成立,則執行顯示"Small"
的這條路徑;若是不成立,則選擇else
分支,else
分支自身包含了第二個if
。若是第二個條件即num
小於 100 成立,且數字的範圍在 10 到 100 之間,則執行顯示"Medium"
的這條路徑。若是上述條件均不知足,則執行最後一條else
分支路徑。
這個程序的模式看起來像這樣:
while
和do
循環現考慮編寫一個程序,輸出 0 到 12 之間的全部偶數。其中一種編寫方式以下所示:
console.log(0); console.log(2); console.log(4); console.log(6); console.log(8); console.log(10); console.log(12);
該程序確實能夠工做,但編程的目的在於減小工做量,而非增長。若是咱們須要小於 1000 的偶數,上面的方式是不可行的。咱們如今所需的是重複執行某些代碼的方法,咱們將這種控制流程稱爲循環。
咱們可使用循環控制流來讓程序執行回到以前的某個位置,並根據程序狀態循環執行代碼。若是咱們在循環中使用一個綁定計數,那麼就能夠按照以下方式編寫代碼:
let number = 0; while (number <= 12) { console.log(number); number = number + 2; } // → 0 // → 2 // … etcetera
循環語句以關鍵字while
開頭。在關鍵字while
後緊跟一個用括號括起來的表達式,括號後緊跟一條語句,這種形式與if
語句相似。只要表達式產生的值轉換爲布爾值後爲true
,該循環會持續進入括號後面的語句。
number
綁定演示了綁定能夠跟蹤程序進度的方式。 每次循環重複時,number
的值都比之前的值多 2。 在每次重複開始時,將其與數字 12 進行比較來決定程序的工做是否完成。
做爲一個實際上有用的例子,如今咱們能夠編寫一個程序來計算並顯示2**10
(2 的 10 次方)的結果。 咱們使用兩個綁定:一個用於跟蹤咱們的結果,一個用來計算咱們將這個結果乘以 2 的次數。 該循環測試第二個綁定是否已達到 10,若是不是,則更新這兩個綁定。
let result = 1; let counter = 0; while (counter < 10) { result = result * 2; counter = counter + 1; } console.log(result); // → 1024
計數器也能夠從1
開始並檢查<= 10
,可是,因爲一些在第 4 章中澄清的緣由,從 0 開始計數是個好主意。
do
循環控制結構相似於while
循環。二者之間只有一個區別:do
循環至少執行一遍循環體,只有第一次執行完循環體以後纔會開始檢測循環條件。do
循環中將條件檢測放在循環體後面,正反映了這一點:
let yourName; do { yourName = prompt("Who are you?"); } while (!yourName); console.log(yourName);
這個程序會強制你輸入一個名字。 它會一再詢問,直到它獲得的東西不是空字符串。 !
運算符會將值轉換爲布爾類型再取反,除了""
以外的全部字符串都轉換爲true
。 這意味着循環持續進行,直到您提供了非空名稱。
在這些例子中,我一直在語句前添加空格,它們是一些大型語句的一部分。 這些都不是必需的 - 沒有它們,計算機也會接受該程序。 實際上,即便是程序中的換行符也是可選的。 若是你喜歡,你能夠將程序編寫爲很長的一行。
塊內縮進的做用是使代碼結構顯而易見。 在其餘塊內開啓新的代碼塊中,可能很難看到塊的結束位置,和另外一個塊開始位置。 經過適當的縮進,程序的視覺形狀對應其內部塊的形狀。 我喜歡爲每一個開啓的塊使用兩個空格,但風格不一樣 - 有些人使用四個空格,而有些人使用製表符。 重要的是,每一個新塊添加相同的空格量。
if (false != true) { console.log("That makes sense."); if (1 < 2) { console.log("No surprise there."); } }
大多數代碼編輯器程序(包括本書中的那個)將經過自動縮進新行來提供幫助。
for
循環許多循環遵循while
示例中看到的規律。 首先,建立一個計數器綁定來跟蹤循環的進度。 而後出現一個while
循環,一般用一個測試表達式來檢查計數器是否已達到其最終值。 在循環體的末尾,更新計數器來跟蹤進度。
因爲這種規律很是常見,JavaScript 和相似的語言提供了一個稍短並且更全面的形式,for
循環:
for (let number = 0; number <= 12; number = number + 2) console.log(number); // → 0 // → 2 // … etcetera
該程序與以前的偶數打印示例徹底等價。 惟一的變化是,全部與循環「狀態」相關的語句,在for
以後被組合在一塊兒。
關鍵字for
後面的括號中必須包含兩個分號。第一個分號前面的是循環的初始化部分,一般是定義一個綁定。第二部分則是判斷循環是否繼續進行的檢查表達式。最後一部分則是用於每一個循環迭代後更新狀態的語句。絕大多數狀況下,for
循環比while
語句更簡短清晰。
下面的代碼中使用了for
循環代替while
循環,來計算2**10
:
var result = 1; for (var counter = 0; counter < 10; counter = counter + 1) result = result * 2; console.log(result); // → 1024
除了循環條件爲false
時循環會結束之外,咱們還可使用一個特殊的break
語句來當即跳出循環。
下面的程序展現了break
語句的用法。該程序的做用是找出第一個大於等於 20 且能被 7 整除的數字。
for (let current = 20; ; current++) { if (current % 7 == 0) break; } } // → 21
咱們可使用餘數運算符(%
)來判斷一個數是否能被另外一個數整除。若是能夠整除,則餘數爲 0。
本例中的for
語句省略了檢查循環終止條件的表達式。這意味着除非執行了內部的break
語句,不然循環永遠不會結束。
若是你要刪除這個break
語句,或者你不當心寫了一個老是產生true
的結束條件,你的程序就會陷入死循環中。 死循環中的程序永遠不會完成運行,這一般是一件壞事。
若是您在(英文版)這些頁面的其中一個示例中建立了死限循環,則一般會詢問您是否要在幾秒鐘後中止該腳本。 若是失敗了,您將不得不關閉您正在處理的選項卡,或者在某些瀏覽器中關閉整個瀏覽器,以便恢復。
continue
關鍵字與break
相似,也會對循環執行過程產生影響。循環體中的continue
語句能夠跳出循環體,並進入下一輪循環迭代。
程序常常須要根據綁定的原值進行計算並更新值,特別是在循環過程當中,這種狀況更加常見。
counter = counter + 1;
JavaScript 提供了一種簡便寫法:
counter += 1;
JavaScript 還爲其餘運算符提供了相似的簡便方法,好比result*=2
能夠將result
變爲原來的兩倍,而counter-=1
能夠將counter
減 1。
這樣能夠稍微簡化咱們的計數示例代碼。
for (let number = 0; number <= 12; number += 2) console.log(number);
對於counter+=1
和counter-=1
,還能夠進一步簡化代碼,counter+=1
能夠修改成counter++
,counter-=1
能夠修改成counter--
。
switch
條件分支咱們不多會編寫以下所示的代碼。
if (x == "value1") action1(); else if (x == "value2") action2(); else if (x == "value3") action3(); else defaultAction();
有一種名爲switch
的結構,爲了以更直接的方式表達這種「分發」。 不幸的是,JavaScript 爲此所使用的語法(它從 C/Java 語言中繼承而來)有些笨拙 - if
語句鏈看起來可能更好。 這裏是一個例子:
switch (prompt("What is the weather like?")) { case "rainy": console.log("Remember to bring an umbrella."); break; case "sunny": console.log("Dress lightly."); case "cloudy": console.log("Go outside."); break; default: console.log("Unknown weather type!"); break; }
你能夠在switch
打開的塊內放置任意數量的case
標籤。 程序會在向switch
提供的值的對應標籤處開始執行,或者若是沒有找到匹配值,則在default
處開始。 甚至跨越了其餘標籤,它也會繼續執行,直到達到了break
聲明。 在某些狀況下,例如在示例中的"sunny"
的狀況下,這能夠用來在不一樣狀況下共享一些代碼(它建議在晴天和多雲天氣外出)。 但要當心 - 很容易忘記這樣的break
,這會致使程序執行你不想執行的代碼。
綁定名中不能包含空格,但不少時候使用多個單詞有助於清晰表達綁定的實際用途。當綁定名中包含多個單詞時能夠選擇多種寫法,如下是能夠選擇的幾種綁定名書寫方式:
fuzzylittleturtle fuzzy_little_turtle FuzzyLittleTurtle fuzzyLittleTurtle
第一種風格可能很難閱讀。 我更喜歡下劃線的外觀,儘管這種風格有點痛苦。 標準的 JavaScript 函數和大多數 JavaScript 程序員都遵循最底下的風格 - 除了第一個詞之外,它們都會將每一個詞的首字母大寫。 要習慣這樣的小事並不困難,並且混合命名風格的代碼可能會讓人反感,因此咱們遵循這個約定。
在極少數狀況下,綁定名首字母也會大寫,好比Number
函數。這種方式用來表示該函數是構造函數。咱們會在第6章詳細講解構造函數的概念。如今,咱們沒有必要糾結於表面上的風格不一致性。
一般,原始代碼並不能傳達你讓一個程序傳達給讀者的全部信息,或者它以神祕的方式傳達信息,人們可能不瞭解它。 在其餘時候,你可能只想包含一些相關的想法,做爲你程序的一部分。 這是註釋的用途。
註釋是程序中的一段文本,而在程序執行時計算機會徹底忽略掉這些文本。JavaScript 中編寫註釋有兩種方法,寫單行註釋時,使用兩個斜槓字符開頭,並在後面添加文本註釋。
let accountBalance = calculateBalance(account); // It's a green hollow where a river sings accountBalance.adjust(); // Madly catching white tatters in the grass. let report = new Report(); // Where the sun on the proud mountain rings: addToReport(accountBalance, report); // It's a little valley, foaming like light in a glass.
//
註釋只能到達行尾。 /*
和*/
之間的一段文本將被忽略,無論它是否包含換行符。 這對添加文件或程序塊的信息塊頗有用。
/* I first found this number scrawled on the back of one of an old notebook. Since then, it has often dropped by, showing up in phone numbers and the serial numbers of products that I've bought. It obviously likes me, so I've decided to keep it. */ const myNumber = 11213;
在本章中,咱們學習並瞭解了程序由語句組成,而每條語句又有可能包含了更多語句。在語句中每每包含了表達式,而表達式還能夠由更小的表達式組成。
程序中的語句按順序編寫,並從上到下執行。你可使用條件語句(if
、else
和switch
)或循環語句(while
、do
和for
)來改變程序的控制流。
綁定能夠用來保存任何數據,並用一個綁定名對其引用。並且在記錄你的程序執行狀態時十分有用。環境是一組定義好的綁定集合。JavaScript 的運行環境中總會包含一系列有用的標準綁定。
函數是一種特殊的值,用於封裝一段程序。你能夠經過functionName(arg1, arg2)
這種寫法來調用函數。函數調用能夠是一個表達式,也能夠用於生成一個值。
若是你不清楚在哪裏能夠找到習題的提示,請參考本書的簡介部分。
每一個練習都以問題描述開始。 閱讀並嘗試解決這個練習。 若是遇到問題,請考慮閱讀練習後的提示。 本書不包含練習的完整解決方案,但您能夠在 eloquentjavascript.net/code 上在線查找它們。 若是你想從練習中學到一些東西,我建議僅在你解決了這個練習以後,或者至少在你努力了很長時間而感到頭疼以後,再看看這些解決方案。
編寫一個循環,調用 7 次console.log
函數,打印出以下的三角形:
# ## ## ### ### #### #####
這裏給出一個小技巧,在字符串後加上.length
能夠獲取字符串的長度。
let abc = "abc"; console.log(abc.length); // → 3
編寫一個程序,使用console.log
打印出從 1 到 100 的全部數字。不過有兩種例外狀況:當數字能被 3 整除時,不打印數字,而打印"Fizz"
。當數字能被 5 整除時(但不能被 3 整除),不打印數字,而打印"Buzz"
。
當以上程序能夠正確運行後,請修改你的程序,讓程序在遇到能同時被 3 與 5 整除的數字時,打印出"FizzBuzz"
。
(這其實是一個面試問題,聽說剔除了很大一部分程序員候選人,因此若是你解決了這個問題,你的勞動力市場價值就會上升。)
編寫一個程序,建立一個字符串,用於表示8×8
的網格,並使用換行符分隔行。網格中的每一個位置能夠是空格或字符"#"
。這些字符組成了一張棋盤。
將字符串傳遞給console.log
將會輸出如下結果:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
當程序能夠產生這樣的輸出後,請定義綁定size=8
,並修改程序,使程序能夠處理任意尺寸(長寬由size
肯定)的棋盤,並輸出給定寬度和高度的網格。