所以 JS 是怎樣一門語言?
JavaScript" rel="nofollow,noindex" target="_blank">JavaScript 俗稱 JS,又稱借S。起源於 Netscape 瀏覽器支援的頁面指令碼,目前已經被 TC39 標準化為 ECMAScript。 所以我們說的 JavaScript 是 ECMAScript 的一種實現,或者說符合 ES 標準的 JavaScript 就是 ECMAScript。
JS 的地位
要討論 JS 的地位必須要提起“網頁”(Web Page),顧名思義是 Web 中的一個頁面,而 Web 是一個巨大的分散式文件。 這一架構的基石是HTML、HTTP 和 URI。之所以沒有 JavaScript 或 Flash、CSS 這些東西, 是因為沒有 HTML 就沒有 Web,沒有 HTTP 就沒有 Web,沒有 URI 就沒有 Web,但沒有 JavaScript 時確實有 Web。
JavaScript 給 HTML 頁面帶來了互動,是 Web2.0 的重要推動技術。在中文圈裡,可以說 Web 就是借 S 和 H5。 JS 的地位如此之高,要歸功於 Web App 的繁榮和開放網頁的衰落。現在已經很少有網頁開放訪問了,大多需要登入甚至身份認證。
綜上,JS 是 Web App 的開發語言。
JS 的繁榮
Web 的設計是成功的,可連結的、人人可訪問的分散式文件。 原本只希望在歐洲核子中心共享資料,短短几年內連線了整個世界,併成功地推動了網際網路的普及。 以至於“網際網路”、“上網”經常就是指 Web,鮮有人知網際網路 指的其實是 IP/TCP 的國際網路互聯。
Web 是網際網路上最成功的平臺。雖然 iOS、Android 在逐漸取代網頁與使用者互動, 但這些平臺始終無法取代 Web 的優勢:比如可連結、比如快速發版。 Native App 也在越來越多地採用 Web 技術和概念,比如 React、小程式、Deep Link、 甚至 Hybrid 直接嵌入。
JavaScript 是讓 WebPage 變成 WebApp 的推手,類似 jQuery、Backbone、Angular 的框架直接推動了 Web2.0, 更多的使用者直接在 Web 上生產內容。從此 JS 走上了“歷史的快車道”:
- 這一過程中出現的 JS 框架(比如 jQuery)徹底改變了 JavaScript 開發方式。我們可以專注業務而不是瀏覽器相容。
- WHATWG 的成立和 HTML5 的釋出,瀏覽器之間的協作加速了 Web 標準的迭代。
- Node.js 的流行(09年已經發布),從此 JavaScript 名正言順地成為通用(General Purpose)程式語言。
- ECMA 組織重新關注 ECMAScript 標準,從 ES6(2015)開始加速迭代。
繁榮的代價
Web 的成功一部分來自開放和互聯的初始設計,另一部分則來自向後相容的原則。很難想象 Android 2.0 的軟體能夠執行在今天的 Android 平臺上, 或者為Macintosh 開發的軟體能夠執行在當今的 Mac 平臺上, 但 1991 年的 HTML 頁面可以非常好地展現在最新的瀏覽器中,而且比現在絕大多數網頁都快速、安全和無障礙,這就是向後相容。 向後相容意味著數以億計的網頁、各種商業的或開源的瀏覽器、伺服器、代理仍然可以正常執行, 像開放和可連結一樣,向後相容也是 Web 的本質特徵之一。
HTML 標準的迭代在 1998 年就已經停滯了,2000 年前後 W3C 開始聚焦於 XHTML2,XForm 等新技術, 由於需要瀏覽器提供一種不向後相容的引擎,XHTML 標準最終不了了之。 這正是 HTML5 成功的關鍵:完全相容既有的 HTML 標準,尤其是既有的瀏覽器實現。 甚至提出如果實現和標準不一致,我們應當更改標準而非實現。以下來自 HTML Living Standard:
The WHATWG was based on several core principles, in particular that technologies need to be backwards compatible, that specifications and implementations need to match even if this means changing the specification rather than the implementations, and that specifications need to be detailed enough that implementations can achieve complete interoperability without reverse-engineering each other. –Introduction
Web 平臺的向後相容是很嚴格的,包括舊的 Bug 也要保持不變。因為有人會依賴這些 Bug。 這就是為什麼在今天的 JS 語言和 API 中仍然充斥著早期的設計缺陷, 這就是為什麼同樣是 JavaScript 可以寫出不同時代的程式碼。就是向後相容的要求讓 JS 變得今天這樣龐雜。
為什麼說龐雜?
JS 標準較多,需要理解什麼宿主環境支援寫怎樣的語法,哪些語法需要開直譯器引數。 比如 Node 6 中可以寫 Arrow function 但不可使用 async/await。
API 標準較多,需要理解什麼瀏覽器中可以用哪些介面。 jQuery 解決了當時絕大多數問題,但今天有更多的 API 開放出來,不是 jQuery 所能涵蓋的。 比如 HTML 標準停滯後,廠商開始開發 DOM API Level2, Level3,…,W3C 則轉而開發 XHTML1、XHTML2; URI/HTTP Spec 由 IETF 維護,而 HTML Spec 由 W3C 維護,今天 WHATWG 又在提供 URL、HTML 等技術的 Living Standard。
事實標準較多,由於 HTML5 之前技術標準的停滯,產生了很多在現有標準框架下解決問題的社群標準。 比如 AMD、Defer、jQuery,還有一些語言增強比如 CoffeeScript (harttle 寧願手寫一個liquidjs 都不去開發liquid-node )、TypeScript, 這些都有額外的學習成本而且不一定符合長期標準。這也是 JS 比較雜的一個原因。
JS 的不一致
下面到了舉例環節。
-
var
。函式作用域、全域性變數和變數提升機制已經無法移除,因此給出let
,const
並引入各種 Lint 工具來避免使用var
。類似的還有eval
,with
。 -
Array.prototype.includes
為什麼不叫contains
。因為有一個廣泛使用的叫MooTools 的工具已經佔用了這個方法名 。 -
XMLHttpRequest
這麼難用為什麼仍然不改?新的 API 叫fetch。前不久W3C webapps 郵件組還在討論 為什麼XMLHttpRequest
在網路錯誤會直接 throw 而非 reject Promise,原因就是向後相容:本來是拋異常的,Promise 化之後仍然不能 reject。 -
不一致的型別轉換。
toString()
會在字串相加時呼叫。比如{toString:()=>'a'} + 'b' === 'ab'
,但相加的雙方型別不同時會相當複雜,這裡有個表格:https://www.andronio.me/2017/10/22/js-operators-incensistency/ -
Symbol
定義有.toString()
但不能與字串相加(會引發TypeError
),要手動Symbol('foo').toString() + 'b'
。 -
===
。不一致的型別轉換使得==
幾乎不可用。""==0
是真,因為""
被轉換為0
;0=="0"
是真,因為0
被轉換為"0"
;但""=="0"
為假,因為兩側都是字串所以不發生型別轉換。 -
typeof
只能判斷基本資料型別和object
,但不能區分null
和Object
,但可以區分object
和function
(二者都不是基本資料型別)。 -
instanceof
只檢查原型,因此只適用於物件。這導致"foo" instanceof String === false
,"foo"
不是字串?。 -
this
指向呼叫者,而呼叫者不一定是所在物件,被呼叫的函式也不一定是物件方法(可以是全域性函式)。這導致了函式作用域而非塊級作用域。 -
Date
API:.getFullYear()
獲取年份,.getDate()
獲取日,.getMonth()
確實獲取月份減一。
無法逐一列舉,下面這些連結提供了更多樂趣:
- https://www.dummies.com/web-design-development/javascript/10-common-javascript-bugs-and-how-to-avoid-them/
- http://www.standardista.com/javascript/15-common-javascript-gotchas/
- https://www.codeproject.com/Articles/182416/A-Collection-of-JavaScript-Gotchas
- https://www.reddit.com/r/ProgrammerHumor/comments/88gniv/old_meme_format_timeless_javascript_quirks/
- https://ponyfoo.com/articles/more-es6-proxy-traps-in-depth
技術展望
今天已經很少有人直接寫 JavaScript 了,更多的是去寫 ES6(遵循 ECMAScript 6th Edition 的 JavaScript), 或者 TypeScript、JSX 等,經過編譯得到執行在瀏覽器或 Node 中的 JavaScript。從這個角度看 JavaScript 已經成為底層語言 。上層語言的迭代才會直接影響到開發體驗。
另一方面,從 HTML5 和 ES6 開始,HTML 標準、DOM 標準、CSS 標準,和 ECMAScript 標準迭代都在加快。這意味著 JavaScript 的各種宿主 API 和語言本身正在快速增強 。 WHATWG 的各種 Living Standard 成為瀏覽器環境的事實標準,MDN 則是這些標準的官方教程。
綜上,開發實踐正在遠離底層的 JavaScript 轉向上層語言;而 JavaScript 也正在變得更好。 當然 JavaScript 的上述“不一致”和遺留問題會長期存在,這也是 JS 繁榮的基礎。