本文摘自《深入理解JavaScript特性》,本書將JavaScript新特性融入簡單易懂的示例中,包括ES6及後續更新,助你大幅提升代碼表達能力。
JavaScript之父Brendan Eich作序推薦。
JavaScript 已經從 1995 年的一個爲了贏得戰略優勢的市場營銷策略,變成了如今(2017 年)世界上使用最廣泛的應用運行平臺中的核心編程語言。該語言不再只是在瀏覽器中運行,現在也用於創建桌面和移動應用,還用於硬件設備,甚至是 NASA 的太空服設計。
JavaScript 是如何做到這一步的,接下來它又會怎麼做呢?
1.1 JavaScript標準簡史
1995 年,NetScape 公司想要構建一個動態的網頁,但 HTML 無法實現這一點。爲此,他們僱用 Brendan Eich 專門爲瀏覽器開發一門功能類似於 Scheme 的語言。Brendan 加入之後,得知上級主管希望這門語言的語法像 Java,而且這一決定已經開始實施。
Brendan 花 10 天寫出了 JavaScript 的第一個原型,主要實現了 Scheme 的一類函數和 Self 的原型等構造。這個初始版 JavaScript 的代號爲 Mocha。它沒有數組,沒有對象字面量,任何錯誤都會給出警告。此外,它也沒有異常處理,這也是如今仍有很多操作返回 NaN
或 undefined
的原因。Brendan 對 DOM0 級和 JavaScript 第一個版本的實現成爲了這門語言標準化的基礎。
1995 年 9 月,Netscape Navigator 2.0 beta 版發佈,JavaScript 的修訂版也內置其中,對外宣傳時名叫 LiveScript。同年 12 月,Navigator 2.0 beta 3 發佈,此時 LiveScript 又重新命名爲 JavaScript(後成爲 Sun 的註冊商標,Sun 現在是 Oracle 旗下公司)。這次發佈後不久,Netscape 公司推出了服務器端 JavaScript 的實現,用於在 Netscape Enterprise Server 上運行腳本,並將其命名爲 LiveWire。① 1996 年,微軟在 IE3 中推出 JScript,即通過逆向工程實現的 JavaScript。JScript 在服務器端也可以在互聯網信息服務器(IIS,Internet information server)中運行。
①1998 年的這個小冊子(https://docs.oracle.com/cd/E19957-01/816-6411-10/contents.htm)詳細介紹了服務器端 JavaScript 以及 LiveWire 的方方面面。
1996 年,ECMA 的一個技術委員會 TC39 將 JavaScript 以名稱 ECMAScript(ES)標準化爲 ECMA-262 規範。爲什麼叫 ECMAScript 呢?因爲 Sun 不同意將 JavaScript 商標轉讓給 ECMA,雖然微軟提議叫 JScript,但其他成員公司又不想用,結果就只能使用 ECMAScript 這個尷尬的名字。
當時,TC39 開會主要就是爭論該採用 Netscape 的 JavaScript,還是微軟的 JScript。儘管如此,該委員會還是取得了成果,他們堅定不移地支持向後兼容,推動引入了嚴格相等運算符(===
和 !==
),不會影響依賴鬆散相等比較算法的現有程序。
ECMA-262 的第一版於 1997 年 6 月發佈。次年 6 月,國際標準化組織對這個規範進行了完善和認真審查,並以 ISO/IEC 16262 的形式發佈,這就是它的第二版。
1999 年 12 月發佈的第三版標準化了正則表達式、switch
語句、do
/whille
、try
/catch
、Object#hasOwnProperty
,以及其他一些特性。其中大部分特性已經可以在 Netscape 的 JavaScript 運行環境 SpiderMonkey 中使用。
之後不久,TC39 發佈了 ES4 規範的草案。ES4 的早期工作直接導致了 2000 年年中 JScript.NET 的產生 ②,並最終促進 2006 年 Flash 中 ActionScript 3 的誕生。③
②可以在微軟網站上找到最初的公告(2000 年 7 月)。
③Brendan Eich 在播客 JavaScript Jabber 中介紹了很多關於 JavaScript 起源的故事。
此時,關於 JavaScript 應該朝哪個方向發展的不同意見導致了規範制定工作停滯不前。對於 Web 標準的發展而言,這時的情形很微妙:微軟幾乎壟斷了 Web 行業,卻對制定標準毫無興趣。
2003 年,AOL 裁掉了 50 名 Netscape 員工 ④,Mozilla 基金會隨之成立。同時,由於微軟佔據了超過 95% 的 Web 瀏覽器市場份額,TC39 被迫解散。
④2003 年 7 月的 The Mac Observer 有該新聞的相關報道。
直到兩年後,Brendan 在 Mozilla 以 Firefox 日益增長的市場份額爲槓桿使得微軟迴歸,ECMA 才得以重新開始 TC39 的相關工作。2005 年年中,TC39 再次開始召開定期會議。對於 ES4,計劃引入模塊系統、類、迭代器、生成器、解構、類型註釋、尾調用優化、代數類型以及其他各類功能。這次的新增內容過於龐大,導致 ES4 一次又一次地延期了。
2007 年,TC39 分裂爲兩派:一派主張推出 ES3.1,即只在 ES3 的基礎上完善改進;另一派則主張推出 ES4,新特性繁多,且亟待規範化。直到 2008 年 8 月 ⑤,兩派才就 ES3.1 達成一致,ES3.1 後來又發展爲 ES5。ES4 的提議似乎廢棄了,但實際上其中很多特性最終寫進了 ES6(達成和解時美其名曰 Harmony,即“和諧”),當然,其中一些特性還在討論,另外少數特性確實已經廢棄、被拒或撤銷。ES3.1 也爲 ES4 的逐步實現奠定了基礎。
⑤2008 年,Brendan Eich 給 es-discuss 發了一封郵件,其中介紹了當時的情況,此時距 ES3 發佈都快 10 年了。
2009 年 12 月,ES3 發佈 10 週年,ECMAScript 第五版發佈。這一版彙集了當時瀏覽器中業已存在的擴展,或者說“事實標準”,增加了 get
和 set
存取器,增強了 Array
原型的函數特性,還引入了反射和內省機制,以及對 JSON 解析和嚴格模式的支持。
2011 年 6 月,經過再次審查和編輯,該規範形成了第三版的國際標準 ISO/IEC 16262:2011,並作爲 ECMAScript 5.1 發佈。
2015 年 6 月,TC39 又花 4 年完成了 ECMAScript 6。第六版是該語言面世以來改動最大的一個版本,實現了許多 ES4 中被推遲到 Harmony 中的提案。本書主要探討的就是 ES6。
在 ES6 的制定過程中,另一個旨在推動 Web 發展的組織 WHATWG(網頁超文本應用技術工作小組)於 2012 年推出了一個文檔,以記錄 ES5.1 和瀏覽器實現在兼容性和可操作性方面的差異。這個工作小組標準化了之前規範中沒有提及的 String#substr
,統一了在 HTML 標籤中包裝字符串的幾種方法,明確寫出了 Object.prototype
中的 __proto__
和 __defineGeter__
等原型屬性,同時還做出了其他一些改進。⑥ 這些工作最終形成了一份獨立的 Web ECMAScript 規範,最終於 2015 年被加入到附錄 B 中。附錄 B 是 ECMAScript 核心規範中的參考部分,這意味着瀏覽器可以不遵照其進行實現。此次更新之後,附錄 B 也成爲了 Web 瀏覽器的規範和要求。
⑥要想了解將 Web ECMAScript 規範合併到主幹時所做的全部更改,請參見 WHATWG 博客。
第六版是 JavaScript 歷史上一個意義重大的里程碑。除了衆多新功能之外,ES6 也是 ECMAScript 成爲持續迭代標準的一個轉折點。
1.2 持續迭代的ECMAScript
ES3 在 10 年內未有重大改變,之後 4 年才推出 ES6。這一漫長曆程清楚地表明 TC39 流程需要改進。之前的修訂流程是最終發佈日期驅動的。只要有議題未能達成一致,下次修訂就會變得遙遙無期。時間一長又會有新的特性提出,進而導致更多拖延。小的修訂總會因大的新增而拖延,而大的新增迫於最終發佈日期的壓力,就會倉促通過修訂,以免造成一拖再拖。
ES6 發佈後,TC39 優化了提案修訂的流程 ⑦,以滿足現代預期:迭代具有經常性和一貫性,且規範的制定要更加民主化。基於這一點,TC39 不再採用古老的 Word 文檔形式,而是使用 Ecmarkup(用於編寫 ECMAScript 規範的 HTML 語言的超集)和 GitHub 來提交需求,大大增加了非成員的提案數量 ⑧,他們就像是外在的參與者一樣。這種新形式是持續的,並且更加透明:最新的規範草案隨時可以查看,而無須像之前那樣,必須從網頁下載 Word 文檔或 PDF 版本。
⑦2013 年 9 月的 PPT,Post-ES6 Spec Process,介紹了優化後的提案修訂流程。
⑧TC39 採納的提案參見 https://mjavascript.com/out/tc39-proposals。
Firefox、Chrome、Edge、Safari 以及 Node.js 對 ES6 規範的支持均已超過 95%⑨。現在我們就可以在這些瀏覽器中使用已經支持的功能,而不用等到它們對 ES6 的支持度達到 100%。
⑨ES6 的瀏覽器兼容性報告參見 https://kangax.github.io/compat-table/es6/。
新的流程引入了 4 個不同的成熟度階段。⑩ 提案越成熟,最終添加到規範中的可能性越大。
⑩TC39 的提案流程文檔參見 https://tc39.github.io/process-document/。
只要還未作爲正式提案提交,關於修改或新增內容的任何討論、想法或者提議都會被視爲有前途的“稻草人”提案(階段 0),但只有 TC39 委員會的成員纔可以創建“稻草人”提案。在編寫本書時,有 10 多個活躍的“稻草人”提案。⑪
⑪“稻草人”提案參見 https://github.com/tc39/proposals/blob/master/stage-0-proposals.md。
階段 1 表示提案被正式提出,希望能夠解決各方關注的問題,釐清與其他提案的交集,並實現問題。這一階段的提案需要明確描述一個具體的問題,並給出該問題的具體解決方案。階段 1 的提案通常包括以下幾方面的內容:高級 API 描述、演示性的用法示例、內部語義和算法的討論。階段 1 的提案可能會隨着流程的推進而發生很大的改變。
階段 2 的提案是規範的初步草案。從這一步開始,需要在運行環境中驗證具體的實現。實現的方式可以是膩子腳本(polyfill)、能讓運行環境支持提案的用戶代碼、原生支持提案內容的引擎實現,也可以是使用構建工具將源代碼轉換、編譯成現有引擎可以執行的代碼。
階段 3 的提案是候選推薦提案。只有規範的編輯和指定的審查人員在最終的規範上簽字確認,提案才能進入階段 3。另外,還需要實現者表示出對該提案感興趣。實際上,只有滿足以下 3 個條件之一,提案才能進入階段 3:某個瀏覽器已經實現該提案,有高度吻合的膩子腳本,有類似 Babel 的實時編譯工具的支持。除了修復使用過程中新發現的問題,階段 3 的提案不會再有其他改動。
要想進入階段 4,提案必須有兩個獨立的實現方案通過驗收測試。進入階段 4 的提案最終會添加到 ECMAScript 的下一版中。
從現在開始,ECMAScript 預計每年都會發布新版本。爲了與年度發版計劃統一,規範的版本從現在開始與出版的年份相關聯。因此,ES6 也就是 ES2015,之後將有 ES2016(而不是 ES7)、ES2017,等等。實際上,ES2015 這個稱呼並沒有被接受,大家還是習慣稱其爲 ES6。ES2016 也是在命名約定改變前就發佈了的,因此有時人們也稱其爲 ES7。由於 ES6 這一名稱已經爲社區普遍接受,我們拋開不談。最終的規範版本將是 ES6、ES2016、ES2017、ES2018,以此類推。
調整後的提案流程加上每年都發布一版的強制約定形成了更加一致的發佈過程。這也意味着規範的修訂版本號變得不再那麼重要。現在的重點是提案的不同階段,我們相信將來大家會越來越少提及 ECMAScript 標準的某個特定修訂版。
1.3 瀏覽器支持和輔助工具
如果能夠有兩個 JavaScript 引擎提供獨立的實現,階段 3 的候選推薦提案最有可能進入下一個版本的規範之中。實際上,只要階段 3 的提案有實驗性的引擎實現或者膩子腳本,再或者可以通過編譯器得到支持,那麼就已經能夠安全地在實際開發中使用了。其實,階段 2 和更早階段的提案也有爲 JavaScript 開發人員所使用的,這也加強了實現者和使用者之間的反饋循環。
Babel 等將代碼作爲輸入並生成 Web 平臺原生支持的輸出(HTML、CSS 或 JavaScript)的編譯器通常稱爲轉譯器(transpiler),它是編譯器的一個子集。如果我們想要在代碼中使用某個 JavaScript 引擎還沒有普遍實現的提案,Babel 之類的編譯器可以幫我們將相應的代碼轉換成現有 JavaScript 實現可以運行的代碼。
代碼轉換是在構建時完成的,因此用戶拿到的是自己的 JavaScript 運行環境所支持的代碼。這一機制降低了對運行環境的要求,也讓 JavaScript 開發者能夠更早地使用新的語言功能和語法。對於規範的編寫者和實現者來說,這也是非常有利的,因爲這樣他們就可以得到可行性、迫切性、可能存在的 bug,以及邊界用例等方面的反饋。
轉譯器可以將 ES6 代碼轉換成瀏覽器普遍可以解釋的 ES5 代碼。這是如今在生產環境中運行 ES6 代碼的最可靠方式:通過構建生成 ES5 代碼,這樣新舊瀏覽器都可以執行。
這種方式同樣適用於 ES7 及後續版本。由於語言規範每年都會發布新版本,我們可以期待編譯器支持 ES2017 輸入、ES2018 輸入,等等。同樣,隨着瀏覽器的支持度越來越好,編譯器可以逐漸降低支持 ES6 輸出、ES7 輸出的複雜性。從這種意義上說,我們可以將 JavaScript 轉譯器看作移動的窗口,其輸入是使用最新語法編寫的代碼,輸出是不影響瀏覽器運行的最新代碼。
接下來我們探討如何在工作中使用 Babel。
1.3.1 Babel轉譯器簡介
Babel 可以將 ES6 代碼編譯成 ES5 代碼。生成的 ES5 代碼很容易看懂,因此非常適合還不完全熟悉新特性的人來理解新特性。
Babel 的在線讀取 - 求值 - 打印循環(REPL,read-evaluate-print loop)轉換器是學習 ES6 的好幫手,無須安裝 Node.js、babel
CLI,也無須手工編譯源碼。
REPL 提供了一個源碼輸入框,用於自動實時編譯,編譯後的代碼位於源碼右側。
我們可以在 REPL 中寫幾行代碼,如下所示。
var double = value => value * 2
console.log(double(3))
// <- 6
我們可以在右側看到轉換後的 ES5 等價代碼,如圖 1-1 所示。更新源碼時,轉譯結果也會實時更新。
圖 1-1:在線 Babel REPL 是交互式學習 ES6 的好工具
Babel REPL 也是用於試驗本書中介紹的一些特性的有效工具。但需要知道的是,Babel 並不轉換新的內置對象,如 Symbol
、Proxy
和 WeakMap
。這些引用會原封不動地保留下來,最終由執行 Babel 輸出代碼的運行環境提供這些內置對象。如果想要支持還沒有實現這些內置對象的運行環境,可以在代碼中引入 babel-polyfill
包。
在一些較老的 JavaScript 版本中,想要語義正確地實現某些特性是很難的,甚至完全不可能。此時可以用膩子腳本彌補這個問題,但膩子腳本通常不能覆蓋所有情況,因此需要做一些妥協。所以,在將轉譯後的使用了內置對象和膩子腳本的代碼發佈到生產環境之前,最好多做一些測試。
考慮到這種情況,最好還是等瀏覽器整體支持這些新的內置對象時再使用它們。我們建議你使用不依賴於這些新內置對象的替代方案。與此同時,學習這些特性也很重要,這樣我們對 JavaScript 語言的理解纔不會落後。
Chrome、Firefox 和 Edge 等現代瀏覽器現在已經支持 ES2015 及後續版本的大部分內容,所以在支持的前提下,可以通過它們的開發者工具來嘗試新特性。產品級應用需要依賴新 JavaScript 特性時,還是推薦使用轉換器進行編譯處理,這樣應用能夠支持更多的 JavaScript 運行環境。
除了 REPL,Babel 還提供了一個在命令行中運行的 Node.js 包,可以通過 Node.js 的包管理工具 npm
來安裝它。
下載 Node.js。安裝
node
後,就可以在終端內使用npm
命令了。
開始之前,我們先創建一個項目目錄以及一個用於描述 Node.js 應用的 package.json 文件。可以通過 npm
在命令行中創建 package.json 文件。
mkdir babel-setup
cd babel-setup
npm init --yes
執行
init
命令時傳遞--yes
參數表示不用詢問我們,可以使用npm
提供的默認值來配置 package.json 文件。
再創建一個 example.js 文件,並在其中加入以下代碼,然後將其保存到剛剛創建的 babel- setup 目錄下的 src 子目錄。
var double = value => value * 2
console.log(double(3))
// <- 6
在常用的終端中輸入以下兩行命令即可安裝 Babel。
npm install babel-cli@6 --save-dev
npm install babel-preset-env@6 --save-dev
通過
npm
安裝的包存放於項目根目錄的 node_modules 目錄下。可以通過創建npm script
命令或使用require
聲明來訪問這些包。
--save-dev
參數會將所安裝的包作爲開發依賴添加到 package.json 文件中。這樣一來,當我們將項目移植到一個新環境時,只須運行npm install
命令就可以重新安裝每個依賴。
@
符號指明瞭包的特定版本。使用@6
則是告訴npm
安裝babel-cli
的6.x
版本中最新的一個。這種偏好限制可以確保我們的應用將來不出問題,因爲它可以確保永遠不會安裝7.0.0
或後續版本,從而避免了7.0.0
之後的版本包含當前無法預見的破壞性變化。
接下來,我們修改 package.json 中的 scripts
屬性的值,如下所示。babel-cli
提供的 babel
命令行工具可以獲取 src 目錄下的全部內容,將它們編譯成目標輸出格式,並將結果保存到 dist 目錄中,並且保留文件的原始目錄結構。
{
"scripts": {
"build": "babel src --out-dir dist"
}
}
加上前面安裝的包,現在我們構成了以下這個超小的 package.json 文件。
{
"scripts": {
"build": "babel src --out-dir dist"
},
"devDependencies": {
"babel-cli": "^6.24.0",
"babel-preset-env": "^1.2.1"
}
}
scripts
對象中列舉的所有命令都可以通過npm run <name>
執行,這種執行方式會臨時修改環境變量$PATH
的值,這樣我們纔可以在系統未全局安裝babel-cli
的情況下找到babel-cli
並在命令行執行。
如果現在在終端內執行 npm run build
,你就能看到生成後的 dist/example.js 文件。輸出的文件和源文件完全相同。這是因爲 Babel 還不知道編譯的目標格式,我們需要先配置它。在 package.json 旁創建一個 .babelrc 文件,並在其中寫入以下 JSON。
{
"presets": ["env"]
}
這裏的 env
就是前面通過 npm
安裝的 babel-preset-env
,它給 Babel 添加了一系列插件,以便將不同的 ES6 代碼轉換爲 ES5。這個預設包含很多插件,其中就有插件將 example.js 中的箭頭函數轉換成 ES5 代碼。env
預設會根據最新瀏覽器已經支持的特性啓用相關的轉換插件。這個預設是可配置的,我們可以決定向後兼容到哪個瀏覽器。兼容的瀏覽器越多,編譯生成的包就越大;兼容的瀏覽器越少,能滿足的用戶就越少。當然,到底什麼配置最合適,最終還是要經過研究才能決定。默認配置是啓用所有轉換插件,兼容儘可能多的運行環境。
再運行一次編譯腳本就能夠看到輸出的 ES5 代碼了。
» npm run build
» cat dist/example.js
"use strict"
var double = function double(value) {
return value * 2
}
console.log(double(3))
// <- 6
接下來我們再介紹一個代碼檢查工具 eslint
,它可以幫助我們確保項目代碼的質量。
1.3.2 使用ESLint提高代碼質量和一致性
開發項目時,我們會發現冗餘或無用的代碼,編寫很多新代碼,刪除無關或沒必要的功能,也會爲適應新架構而來回搬運代碼。隨着代碼越寫越多,團隊規模也會隨之變化。剛開始可能只有幾個人,甚至只有一個人,但隨着項目規模越來越大,參與編碼的人也會越來越多。
代碼檢查工具可以幫助我們發現語法錯誤。現代檢查工具通常都是可定製的,允許我們建立適合自己團隊的代碼約定。堅守一致的代碼風格和質量基準可以使團隊的編碼風格趨於一致。不同的團隊成員可能會對代碼風格有不同的意見,但有了代碼檢查工具和協商一致的配置後,這些意見就變成了明確可遵循的規則。
首先是確保程序能被解析,其次可能要防止 throw
拋出字符串字面量作爲異常,或者不允許在生產環境中使用 console.log
和 debugger
語句。然而,要求所有函數調用都只能有一個參數就有點過了。
雖然代碼檢查工具能夠定義並強制推行一種編碼風格,但定義規則時也不能太任性。如果規則太嚴格,可能會影響開發效率,得不償失。反之,規則太寬鬆的話,代碼就不能保持統一風格。
要想把握好這個度,就應該儘量不使用多數情況下都不能改善程序的規則。新增一條規則時,應當捫心自問,添加它能否顯著改善現有代碼庫以及未來的新代碼?
ESLint 是一個現代代碼檢查工具,它集合了一些插件,具有不同的規則,支持自定義。我們可以決定不遵守規則時是輸出警告還是導致停機錯誤。與安裝 babel
一樣,我們也可以通過 npm
安裝 eslint
。
npm install eslint@3 --save-dev
接下來我們需要配置 ESLint。因爲本地安裝了 eslint
,所以可以在 node_modules/.bin 中找到它的命令行工具。執行以下命令能夠引導我們配置 ESlint。首先,告訴它我們想使用一套流行的風格,然後選擇 Standard⑫,最後選擇 JSON 格式的配置文件。
⑫注意,Standard 是一種自我宣告,並未由任何官方組織進行實際的標準化。其實,只要能夠保持統一,使用哪種風格並不重要。在閱讀項目代碼時,一致性有助於減少困擾。Airbnb 風格指南也是很受歡迎的,與 Standard 不同,它默認不可省略分號。
./node_modules/.bin/eslint --init
? How would you like to configure ESLint?
Use a popular style guide
? Which style guide do you want to follow? Standard
? What format do you want your config file to be in? JSON
除了個別規則,eslint
還支持擴展預定義規則,這些規則以 Node.js 包的形式存在。在多項目甚至社區中共享配置時,這會很方便。選擇 Standard 後,可以看到 ESLint 向 package.json 中添加了一些依賴,即包含預設 Standard 規則的包,同時創建了一個名爲 .eslintrc.json 的文件,其中包含以下內容。
{
"extends": "standard",
"plugins": [
"standard",
"promise"
]
}
直接引用包含 npm
實現細節的 node_modules/.bin 目錄並不好。雖然前面初始化 ESLint 配置時這麼引用了,但其實應該避免這樣做,而且檢查代碼時也不應該每次都那麼輸入一遍。爲此,我們在 packge.json 中添加一個腳本命令。
{
"scripts": {
"lint": "eslint ."
}
}
前面安裝 Babel 時提到過,npm run
在執行腳本命令時會將 node_modules 加入 PATH
環境變量。這樣一來,在檢查代碼時,只要執行 npm run lint
,npm
就會在 node_modules 目錄中找到 ESLint CLI。
我們來看看以下的示例文件 example.js,其中的代碼故意沒有遵守規則,以演示 ESLint 具體做了什麼。
var goodbye='Goodbye!'
function hello(){
return goodbye}
if(false){}
執行 lint
腳本命令時,ESLint 會標識文件中所有錯誤的地方,如圖 1-2 所示。
圖 1-2:ESLint 可以幫助我們檢查語法錯誤,保持代碼風格統一
如果在執行命令時傳遞 --fix
參數,那麼 ESLint 能夠自動修復大多數的風格問題。在 package.json 中加入以下腳本命令。
{
"scripts": {
"lint-fix": "eslint . --fix"
}
}
此時執行 lint-fix
只會看到兩個錯誤:未使用 hello
以及 false
是一個不變的條件。其他錯誤都已經修復了,結果如以下代碼所示。上述兩個錯誤沒有修復,因爲 ESLint 會保持中立,不對代碼語義做預設推斷,以免導致語義改變。這樣一來,--fix
就能幫助我們有效解決編碼風格問題,同時又不會破壞邏輯。
var goodbye = 'Goodbye!'
function hello() {
return goodbye
}
if (false) {}
prettier
也是一種代碼檢查工具,可用於自動格式化代碼。我們可以配置prettier
自動重寫代碼,以確保代碼遵循設定的首選項,如使用給定的空格縮進,統一使用單引號或雙引號,末尾自動加逗號,或者限制最大行長度。
現在我們知道了如何將現代 Javascript 代碼編譯成每個瀏覽器都能理解的代碼,以及如何正確檢查和格式化代碼。接下來概述一下 ES6 的特性,並展望一下 JavaScript 的未來。
1.4 ES6特性
ES6 規範足足有 566 頁,是 ES5.1 規範 258 頁的兩倍多。ES6 的主要變化可以歸納爲以下幾類:
- 語法糖
- 新機制
- 更好的語義
- 更多的內置對象和方法
- 對原有限制的非破壞性解決方案
語法糖是 ES6 中最重要的組成部分,包括用新類來表達對象繼承、箭頭函數以及屬性值簡寫等一系列更簡潔的語法。此外,解構、剩餘參數以及擴展運算符等我們將介紹的特性也提供了更加語義化的編程方式。第 2 章和第 3 章將介紹 ES6 中的這部分內容。
ES6 提供了幾種新機制來描述異步流程:代表一個操作最終結果的 Promise、代表一系列值的迭代器,以及能產生一系列值的特殊迭代器——生成器。基於這些新概念和結構,ES2017 提供了 async
/await
,讓我們可以像編寫同步代碼那樣編寫異步代碼。第 4 章將介紹這裏提到的迭代及流控制機制。
在 JavaScript 中,使用任意字符串作爲鍵的普通對象來創建映射是很常見的。如果鍵值來自用戶輸入,且沒有驗證,那麼極有可能產生漏洞。爲此,ES6 引入了一些新的原生內置對象來管理集合和映射,並且沒有隻能使用字符串作爲鍵的限制。第 9 章將介紹相關內容。
代理對象用於重新定義可以通過 JavaScript 反射完成的操作。代理對象和其他語境中的代理類似,比如網絡路由中所說的代理。它可以攔截 JavaScript 對象的任何交互,比如屬性的定義、刪除以及訪問。鑑於代理的工作機制,我們很難用膩子腳本全面實現其功能:當然,膩子腳本是有的,只是在某些場景下其實現與規範不符。第 6 章將介紹代理。
除了新的內置對象,ES6 還對 Number
、Math
、Array
以及 String
對象進行了一定的擴展。第 7 章將介紹添加在這些內置對象上的一系列新實例方法和靜態方法。
ES6 還爲 JavaScript 引入了一個原生模塊系統。第 8 章從 Node.js 使用的 CommonJS 模塊系統講起,然後詳細講解 JavaScript 原生模塊的語義。
因爲 ES6 引入了太多修改,所以很難做到將其新特性與現有 JavaScript 知識自然整合。爲此,第 9 章將用一整章的篇幅來分析每個特性的優點和重要性,以便你對 ES6 建立起初步的概念,並基於此開始使用 ES6。
1.5 JavaScript的未來
JavaScript 語言已經從 1995 年一門沒啥名氣的語言發展爲今天這樣一門強大的語言。雖然 ES6 向前跨越了一大步,卻遠遠未到終點。鑑於每年都會有新的規範發佈,如何跟上新規範發佈的步伐就很重要了。
瞭解 1.2 節中介紹的持續迭代流程後,我們知道,要想跟進標準,首先就要定期訪問 TC39 提案庫 ⑬。我們要時刻關注候選推薦提案(即階段 3 的提案),因爲這些提案最有可能加入新規範。
⑬TC39 收錄的所有提案參見 https://mjavascript.com/out/tc39-proposals。
用一本書來介紹一門快速發展的語言是不太可能的。因此,關注 TC39 提案庫、訂閱週刊 ⑭、閱讀 JavaScript 博客 ⑮ 是及時跟進 JavaScript 最新進展的有效方式。
⑭這種電子週刊很多,如 Pony Foo Weekly、Javascript Weekly。
⑮Pony Foo 網站上有很多關於 ECMAScript 開發的文章,Axel Rauschmayer 也寫了很多關於這方面的文章。
撰寫本書時,開發者期待已久的 Async 函數已經加入規範,並在 ES2017 中發佈。此時此刻有很多候選提案,比如支持異步加載原生 JavaScript 模塊的動態 import()
,以及使用 ES6 中針對參數列表和數組引入的剩餘和擴展運算符來枚舉對象屬性。
雖然本書的主要關注點是 ES6,但我們同樣會學習重要的候選推薦,如剛剛提及的 Async 函數、動態 import()
調用、對象剩餘 / 擴展,以及其他內容。