JavaScript程式設計精解中文第三版零、前言

apachecn_飛龍發表於2018-06-01

零、前言

原文:Introduction

譯者:飛龍

協議:CC BY-NC-SA 4.0

自豪地採用谷歌翻譯

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

We think we are creating the system for our own purposes. We believe we are making it in our own image… But the computer is not really like us. It is a projection of a very slim part of ourselves: that portion devoted to logic, order, rule, and clarity.

Ellen Ullman,《Close to the Machine: Technophilia and its Discontents》

這是一本關於指導電腦的書。時至今日,計算機就像螺絲刀一樣隨處可見,但相比於螺絲刀而言,計算機更復雜一些,並且,讓他們做你想讓他們做的事情,並不總是那麼容易。

如果你讓計算機執行的任務是常見的,易於理解的任務,例如向你顯示你的電子郵件,或像計算器一樣工作,則可以開啟相應的應用並開始工作。 但對於獨特的或開放式的任務,應用可能不存在。

這就是程式設計可能出現的地方。程式設計是構建一個程式的行為 – 它是一組精確的指令,告訴計算機做什麼。 由於計算機是愚蠢的,迂腐的野獸,程式設計從根本上是乏味和令人沮喪的。

幸運的是,如果你可以克服這個事實,並且甚至可以享受愚蠢機器可以處理的嚴謹思維,那麼程式設計可以是非常有益的。 它可以讓你在幾秒鐘內完成手動操作。 這是一種方法,讓你的電腦工具去做它以前做不到的事情。 它也提供了抽象思維的優秀練習。

大多數程式設計都是用程式語言完成的。 程式語言是一種人工構建的語言,用於指導計算機。 有趣的是,我們發現與電腦溝通的最有效的方式,與我們彼此溝通的方式相差太大。 與人類語言一樣,計算機語言可以以新的方式組合詞語和片語,從而可以表達新的概念。

在某種程度上,基於語言的介面,例如 80 年代和 90 年代的 BASIC 和 DOS 提示符,是與計算機互動的主要方法。 這些已經在很大程度上被視覺介面取代,這些視覺介面更容易學習,但提供更少的自由。 計算機語言仍然存在,如果你知道在哪裡看到。 每種現代 Web 瀏覽器都內建了一種這樣的語言,即 JavaScript,因此幾乎可以在所有裝置上使用。

本書將試圖讓你足夠了解這門語言,從而完成有用和有趣的東西。

關於程式設計

除了講解 JavaScript 之外,本書也會介紹一些程式設計的基本原則。程式設計還是比較複雜的。程式設計的基本規則簡單清晰,但在這些基本規則之上構建的程式卻容易變得複雜,導致程式產生了自己的規則和複雜性。即便程式是按照你自己的思路去構建的,你也有可能迷失在程式碼之間。

在閱讀本書時,你有可能會覺得書中的概念難以理解。如果你剛剛開始學習程式設計,那麼你估計還有不少東西需要掌握呢。如果你想將所學知識融會貫通,那麼就需要去多參考和學習一些資料。

是否付出必要的努力完全取決於你自己。當你閱讀本書的時候發現任何難點,千萬不要輕易就對自己的能力下結論。只要能堅持下去,你就是好樣的。稍做休息,複習一下所學的知識點,始終確保自己閱讀並理解了示例程式和相關的練習。學習是一項艱鉅的任務,但你掌握的所有知識都屬於你自己,而且今後的學習道路會愈加輕鬆。

當行動無利可圖時,就收集資訊;當資訊無利可圖時,就休息。

Ursula K. Le Guin,《The Left Hand of Darkness》

一個程式有很多含義:它是開發人員編寫的一段文字、計算機執行的一段指令集合、計算機記憶體當中的資料以及控制記憶體中資料的操作集合。我們通常很難將程式與我們日常生活中熟悉的事物進行對比。有一種表面上比較恰當的比喻,即將程式視作包含許多元件的機器,為了讓機器正常工作,這些元件通過內部通訊來實現整個機器的正常運轉。

計算機是一臺物理機器,充當這些非物質機器的載體。計算機本身並不能實現多麼複雜的功能,但計算機之所以有用是因為它們的運算速度非常快。而程式的作用就是將這些看似簡單的動作組合起來,然後實現複雜的功能。

程式是思想的結晶。編寫程式不需要什麼物質投入,它很輕量級,通過我們的雙手創造。

但如果不稍加註意,程式的體積和複雜度就會失去控制,甚至程式碼的編寫者也會感到迷惑。在可控的範圍內編寫程式是程式設計過程中首要解決的問題。當程式執行時,一切都是那麼美好。程式設計的精粹就在於如何更好地控制複雜度。質量高的程式的複雜度都不會太高。

很多開發人員認為,控制程式複雜度的最好方法就是避免使用不熟悉的技術。他們制定了嚴格的規則(“最佳實踐”),並小心翼翼地呆在他們安全區內。

這不僅無聊,而且也是無效的。新問題往往需要新的解決方案。程式設計領域還很年輕,仍然在迅速發展,並且多樣到足以為各種不同的方法留出空間。在程式設計中有許多可怕的錯誤,你應該繼續犯錯,以便你能理解它們。好的程式看起來是什麼樣的感覺,是在實踐中發展的,而不是從一系列規則中學到的。

為什麼編語言重要

在計算技術發展伊始,並沒有程式語言這個概念。程式看起來就像這樣:

00110001 00000000 00000000
00110001 00000001 00000001
00110011 00000001 00000010
01010001 00001011 00000010
00100010 00000010 00001000
01000011 00000001 00000000
01000001 00000001 00000001
00010000 00000010 00000000
01100010 00000000 00000000

該程式計算數字 1~10 之和,並列印出結果:1+2+...+10=55。該程式可以執行在一個簡單的機器上。在早期計算機上程式設計時,我們需要在正確的位置設定大量開關陣列,或在紙帶上穿孔並將紙帶輸入計算機中。你可以想象這個過程是多麼冗長乏味且易於出錯。即便是編寫非常簡單的程式,也需要有經驗的人耗費很大精力才能完成。編寫複雜的程式則更是難上加難。

當然了,手動輸入這些晦澀難懂的位序列(1 和 0)來編寫程式的確能讓程式設計師感到很有成就感,而且能給你的職業帶來極大的滿足感。

在上面的程式中,每行都包含一條指令。我們可以用中文來描述這些指令:

  1. 將數字 0 儲存在記憶體地址中的位置 0。

  2. 將數字 1 儲存在記憶體地址的位置 1。

  3. 將記憶體地址的位置 1 中的值儲存在記憶體地址的位置 2。

  4. 將記憶體地址的位置 2 中的值減去數字 11。

  5. 如果記憶體地址的位置 2 中的值是 0,則跳轉到指令 9。

  6. 將記憶體地址的位置 1 中的值加到記憶體地址的位置 0。

  7. 將記憶體地址的位置 1 中的值加上數字 1。

  8. 跳轉到指令 3。

  9. 輸出記憶體地址的位置 0 中的值。

雖說這已經比一大堆位序列要好讀了許多,但仍然不清晰。使用名稱而不是數字用於指令和儲存位置有所幫助:

 Set “total” to 0.
 Setcountto 1.
[loop]
 Set “compare” tocount”.
 Subtract 11 from “compare”.
 If “compare” is zero, continue at [end].
 Addcountto “total”.
 Add 1 tocount”.
 Continue at [loop].
[end]
 Output “total”.

現在你能看出該程式是如何工作的嗎?前兩行程式碼初始化兩個記憶體位置的值:total用於儲存累加計算結果,而count則用於記錄當前數字。你可能覺得compare的那行程式碼看起來有些奇怪。程式想根據count是否等於 11 來決定是否應該停止執行。因為我們的機器相當原始,所以只能測試一個數字是否為 0,並根據它做出決策。因此程式用名為compare的記憶體位置存放count–11的值,並根據該值是否為 0 決定是否跳轉。接下來兩行將count的值累加到結果上,並將count加 1,直到count等於11為止。

下面使用 JavaScript 重新編寫了上面的程式:

let total = 0, count = 1;
while (count <= 10) {
  total += count;
  count += 1;
}
console.log(total);
// → 55

這個版本的程式得到了一些改進。更為重要的是,我們再也不需要指定程式如何來回跳轉了,而是由while結構負責完成這個任務。只要我們給予的條件成立,while語句就會不停地執行其下方的語句塊(包裹在大括號中)。而我們給予的條件是count<=10,意思是“count小於等於 10”。我們再也不需要建立臨時的值並將其與 0 比較,那樣的程式碼十分煩瑣。程式語言的一項職責就是,能夠幫助我們處理這些煩瑣無趣的邏輯。

在程式的結尾,也就是while語句結束後,我們使用console.log操作來輸出結果。

最後,我們恰好有rangesum這類方便的操作。下面程式碼中的range函式用於建立數字集合,sum函式用於計算數字集合之和:

console.log(sum(range(1, 10)));
// → 55

我們可以從這裡瞭解到,同一個程式的長度可長可短,可讀性可高可低。第一個版本的程式晦澀難懂,而最後一個版本的程式則接近於人類語言的表達方式:將 1~10 範圍內的數字之和記錄下來(我們會在後面的章節中詳細介紹如何編寫sumrange這樣的函式)。

優秀的程式語言可以為開發人員提供更高層次的抽象,使用類似於人類語言的方式來與計算機進行互動。它有助於省略細節,提供便捷的積木(比如whileconsole.log),允許你定義自己的積木(比如sumrange函式),並使這些積木易於編寫。。

什麼是 JavaScript

JavaScript 誕生於 1995 年。起初,Netscape Navigator 瀏覽器將其運用在網頁上新增程式。自此以後,各類主流圖形網頁瀏覽器均採用了 JavaScript。JavaScript 使得現代網頁應用程式成為可能 —— 使用 JavaScript 可以直接與使用者互動,從而避免每一個動作都需要重新載入頁面。但有許多傳統網站也會使用 JavaScript 來提供實時互動以及更加智慧的表單功能。

JavaScript 其實和名為Java的程式設計語言沒有任何關係。起了這麼一個相似的名字完全是市場考慮使然,這並非是一個明智的決定。當 JavaScript 出現時,Java 語言已在市場上得到大力推廣且擁有了極高人氣,因此某些人覺得依附於 Java 的成功是個不錯的主意。而我們現在已經無法擺脫這個名字了。

在 JavaScript 被廣泛採用之後,ECMA 國際制訂了一份標準文件來描述 JavaScript 的工作行為,以便所有聲稱支援 JavaScript 的軟體都使用同一種語言。標準化完成後,該標準被稱為 ECMAScript 標準。實際上,術語 ECMAScript 和 JavaScript 可以交換使用。它們不過是同一種語言的兩個名字而已。

許多人會說 JavaScript 語言的壞話。這其中有很多這樣的言論都是正確的。當被要求第一次使用 JavaScript 編寫程式碼時,我當時就覺得這門語言難以駕馭。JavaScript 接受我輸入的任何程式碼,但是又使用和我的想法完全不同的方式來解釋程式碼。由於我沒有任何線索知道我之前做了什麼,因此我需要做出更多工作,但這也就存在一個實際問題:我們可以自由使用 JavaScript,而這種自由卻幾乎沒有限度。這種設計其實是希望初學者更容易使用 JavaScript 編寫程式。但實際上,系統不會指出我們錯在何處,因此從程式中找出問題變得更加棘手。

但這種自由性也有其優勢,許多技術在更為嚴格的語言中不可能實現,而在 JavaScript 中則留下了實現的餘地,正如你看到的那樣(比如第十章),有些優勢可以彌補 JavaScript 的一些缺點。在正確地學習 JavaScript 並使用它工作了一段時間後,我真正喜歡上了 JavaScript。

JavaScript 版本眾多。大約在 2000~2010 年間,這正是 JavaScript 飛速發展的時期,瀏覽器支援最多的是 ECMAScript 3。在此期間,ECMA 著手製定 ECMAScript 4,這是一個雄心勃勃的版本,ECMA 計劃在這個版本中加入許多徹底的改進與擴充套件。但由於 ECMAScript 3 被廣泛使用,這種過於激進的修改必然會遭遇重重阻礙,最後 ECMA 不得不於 2008 年放棄了版本 4 的制定。這就產生了不那麼雄心勃勃的版本 5,這只是一些沒有爭議的改進,出現在 2009 年。 然後版本 6 在 2015 年誕生,這是一個重大的更新,其中包括計劃用於版本 4 的一些想法。從那以後,每年都會有新的更新。

語言不斷髮展的事實意味著,瀏覽器必須不斷跟上,如果你使用的是較老的瀏覽器,它可能不支援每個特性。 語言設計師會注意,不要做任何可能破壞現有程式的改變,所以新的瀏覽器仍然可以執行舊的程式。 在本書中,我使用的是 2017 版的 JavaScript。

Web 瀏覽器並不是唯一一個可以執行 JavaScript 的平臺。有些資料庫,比如 MongoDB 和 CouchDB,也使用 JavaScript 作為指令碼語言和查詢語言。一些桌面和伺服器開發的平臺,特別是 Node.js 專案(第二十章介紹),為瀏覽器以外的 JavaScript 程式設計提供了一個環境。

程式碼及相關工作

程式碼是程式的文字內容。本書多數章節都介紹了大量程式碼。我相信閱讀程式碼和編寫程式碼是學習程式設計不可或缺的部分。嘗試不要僅僅看一眼示例,而應該認真閱讀並理解每個示例。剛開始使用這種方式可能會速度較慢併為程式碼所困惑,但我堅信你很快就可以熟能生巧。對待習題的方法也應該一樣。除非你確實已經編寫程式碼解決了問題,否則不要假設你已經理解了問題。

建議讀者應嘗試在實際的 JavaScript 直譯器中執行習題程式碼。這樣一來,你就可以馬上獲知程式碼工作情況的反饋,而且我希望讀者去做更多的試驗,而不僅僅侷限於習題的要求。

可以在 http://eloquentjavascript.net/ 中查閱本書的線上版本,並執行和實驗本書中的程式碼。也可以在線上版本中點選任何程式碼示例來編輯、執行並檢視其產生的輸出。在做習題時,你可以訪問 http://eloquentjavascript.net/code/,該網址會提供每個習題的初始程式碼,讓你專心於解答習題。

如果想要在本書提供的沙箱以外執行本書程式碼,需要稍加註意。許多的示例是獨立的,而且可以在任何 JavaScript 環境下執行。但後續章節的程式碼大多數都是為特定環境(瀏覽器或者 Node.js)編寫的,而且只能在這些特定環境下執行程式碼。此外,許多章節定義了更大的程式,這些章節中出現的程式碼片段會互相依賴或是依賴於一些外部檔案。本書網站的沙箱提供了 zip 壓縮檔案的連結,該檔案包含了所有執行特定章節程式碼所需的指令碼和資料檔案。

本書概覽

本書包括三個部分。前十二章討論 JavaScript 語言本身的一些特性。接下來的 8 章討論網頁瀏覽器和 JavaScript 在網頁程式設計中的實踐。最後兩章專門講解另一個使用 JavaScript 程式設計的環境 —— Node.js。

縱觀本書,共有 5 個專案實戰章,用於講解規模較大的示例程式,你可以通過這些章來仔細品味真實的程式設計過程。根據專案出現次序,我們會陸續構建遞送機器人(7)、程式設計語言(12)、平臺遊戲(16)、畫素繪圖程式(19)和一個動態網站(21)。

本書介紹程式語言時,首先使用4章來介紹 JavaScript 語言的基本結構,包括第二章控制結構(比如在本前言中看到的while單詞)、第三章函式(編寫你自己的積木)和第四章資料結構。此後你就可以編寫簡單的程式了。接下來,第五章和第六章介紹函式和物件的運用技術,以編寫更加抽象的程式碼並以此來控制複雜度。

介紹完第一個專案實戰(7)之後,將會繼續講解語言部分,例如第八章錯誤處理和 bug 修復、第九章正規表示式(處理文字資料的重要工具)、第十章模組化(解決複雜度的問題)以及第十一章非同步程式設計(處理需要時間的事件)。第二個專案實戰章節(12)則是對本書第一部分的總結。

第二部分(第十三章到第十九章),闡述了瀏覽器 JavaScript 中的一些工具。你將會學到在螢幕上顯示某些元素的方法(第十四章與第十七章),響應使用者輸入的方法(第十五章)和通過網路通訊的方法(第十八章)。這部分又有兩個專案實戰章節。

此後,第二十章闡述 Node.js,而第二十一章使用該工具構建一個簡單的網頁系統。

本書版式約定

本書中存在大量程式碼,程式(包括你迄今為止看到的一些示例)程式碼的字型如下所示:

function factorial(n) {
  if (n == 0) {
    return 1;
  } else {
    return factorial(n - 1) * n;
  }
}

為了展示程式產生的輸出,本書常在程式碼後編寫程式碼期望輸出,輸出結果前會加上兩個反斜槓和一個箭頭。

console.log(factorial(8));
// → 40320

祝好運!


相關文章