2-0

JavaScript傳奇:從跑龍套到挑大樑的程式語言

接觸程式設計幾十年,看過多少程式語言的起起落落,這其中最讓我驚奇的,就是JavaScript語言了。許多人可能聽過JavaScript誕生的故事:1995年5月,Netscape(網景)的Brendan Eich在公司要求下,花了10天創造出來的。只有10天!這麼倉促寫出來的程式語言,如今卻跑在全世界幾十億台運算裝置上。至今JavaScript開發者仍急速增長,並雄踞「GitHub熱門程式語言排行榜」榜首。

當年最耀眼的明星是Java(準確的說是Java applet,今年剛被Oracle宣告終止的一種技術)。我印象還很深刻:1995年我在Stanford唸研究所的時候,有一天在同學宿舍的電腦看到瀏覽器裡跳來跳去的Java Duke,當時大家那種興奮的心情。那是我們第一次在瀏覽器上看到會動的東西啊!至於JavaScript(當時叫LiveScript),大家跟本聽都沒聽過,更不用說去研究了。如果程式語言的世界也有勵志故事,JavaScript的故事肯定能激勵其它程式語言:從小很窮幫人賣一些雜貨,沒人要做的髒活、苦活它都願意幹,辛苦奮鬥十幾年,終於有了今天的成就……


GitHub top languages by GitHub.com

JavaScript簡史

進展緩慢的15年

JavaScript誕生於1995年。我寫JavaScript,是從1997年的Netscape/IE4瀏覽器上開始的。在那個大家都用C寫CGI程式、用Perl語言就已經很潮的年代,JavaScript不過是開發過程中的配角,那種電影結尾roll字幕才會出現名字的小角色。當時它多半負責檢查表單、alert訊息、popup視窗這類雜事。後來DHTML出現,JavaScript可以修改網頁元件,戲份多了一些,但仍屬於配角。直到2004年Gmail推出,帶動XHR/Ajax相關技術發展,它才逐漸成為網站前端開發的主角。接下來幾年,JavaScript與Adobe Flex分庭抗禮,成為當時rich client應用程式開發的兩大陣營(也許勉強再加上微軟Silverlight)。

然而,JavaScript語言的粗糙原始、加上瀏覽器(特別是IE)相容性這兩大問題,15年來並未獲得改善。用JavaScript開發應用程式,一直是件令人痛苦的事,是軟體開發者能不碰就不碰的語言。YUI、jQuery等程式庫相繼出現,廣受歡迎,減輕了開發者的痛苦,但這並未改變JavaScript語言本身存在重大缺陷的事實。與C++、C#、Java、Python這些成熟語言比起來,JavaScript顯得很寒酸,像個玩具語言、拼裝車。

突飛猛進的5年

2010年ES5(JavaScript語言標準──ECMAScript第5版)的推出,與2011年Node.js/npm的出現,使事情開始發生變化。ES5看似改變不大,其實解決了很多關鍵問題,是JavaScript邁向成熟語言的分水嶺。Node.js則是幫助JavaScript切斷與瀏覽器的臍帶,使它成為一門獨立存在的語言,開啟了各種全新應用;於此同時,npm解決了JavaScript多年來缺乏模組化套件資源庫的問題,各種JavaScript套件在npm平台如雨後春筍般冒出來,很多公司紛紛開始採用JavaScript開發大型前後端程式。(我在2012年寫的一篇英文blog:“JavaScript, a rising star”,記錄了我當時看到的一些現象,也預言JavaScript的興起,當然很多人也都看到了這一趨勢。)

2015年6月,眾多開發者期待已久的ES6(ECMAScript第6版)終於定案。對JavaScript開發者而言,這可是一件大事!5年前的ES5,修正了JavaScript諸多缺陷,並促使IE瀏覽器往標準靠攏;ES6則為JavaScript注入現代語言的重要特性,使JavaScript成為一個足以挑大樑的全方位程式語言。其實早在ES6正式定案之前,JavaScript社群早已迫不及待開始用新標準開發程式了,各家瀏覽器也紛紛提早支援ES6。比起當年ES5支援慢吞吞才到位的景象,不可同日而語。

不過在這期間,JavaScript發生過一次重大危機。

io.js分裂危機

過去Node.js的發展路線(roadmap)由Joyent公司主導,然而Node.js社群是由眾多志願者貢獻心力,才得以蓬勃發展。2014年12月,幾位Node.js核心開發者不滿Joyent幾近獨裁的主導方式,決定另起爐灶,成立io.js計劃。(Node.js的授權條款允許任何人重製改寫程式碼,只是不能使用Node.js的名字。)一開始Joyent態度強硬,無意改變他們的做法,但隨著越來越多使用者轉投io.js陣營的懷抱、加上Node.js因核心開發者的離開而陷入停滯,Joyent終於讓步,接受io.js提出的條件,成立一個中立的committee主導未來Node.js發展。2015年6月,雙方陣營一致通過,在新成立的Node.js Foundation之下共同合作。2015年9月,由Node.js v0.12與io.js v3.3程式碼合併而成的Node v4.0正式發佈,Node.js社群的分裂危機也隨之落幕。

以下我就分別介紹ES6的重大特色,以及我認為JavaScript專案開發應該注意的要點/常用工具。

一、ES6重大特色

ES6是JavaScript有史以來的最大改變,匯集了眾多頂尖工程師的智慧。與Java、C#等由單一商業公司主導,或Perl、Python、Ruby/RoR等由BDFL(Benevolent Dictator For Life──仁慈的獨裁者,一種技術開發社群給該技術創始人的稱號)主導的程式語言不同,現代JavaScript的發展,是由一些JavaScript社群的意見領袖、及許多Internet重量級公司指派的代表所組成的ECMA TC39 Committee,在廣納各方意見之後做出的決定。這避免了過度仰賴單一公司的問題,也減少技術創始者個人偏好造成的影響。

Arrow Function

Arrow function是JavaScript語法上的擴充,將原本function (x) {...}.bind(this)簡化為x => ...型式。別小看這個改變,它使得JavaScript能充份發揮functional programming特點,也使程式的可讀性、維護性及程式員生產力大為提高,是我最欣賞的ES6特色之一。

早期JavaScript雖然陽春,卻從一開始就有了first-class function,也就是函式在語言中是一等公民(first-class citizen),可以被當作一般物件傳遞、回傳、指派。在那個物件導向程式設計獨領風騷的年代,這不能不說是Brendan Eich的遠見。(他的另一遠見,是讓JavaScript一開始就具備prototype物件導向的特性,這兩件事加在一起,使得JavaScript成為一個深具潛力的語言。)以下是Brendan Eich的一段話

I’m not proud, but I’m happy that I chose Scheme-ish first-class functions and Self-ish (albeit singular) prototypes as the main ingredients.

Object-Oriented Programming

ES6終於賦與了JavaScript物件導向程式設計(Object-Oriented Programming)的正式語法,包括classextendsconstructor等等。在這之前,JavaScript雖然能寫物件導向程式設計,但其語法蹩腳、違反直覺又不統一,每個程式員有他偏好的寫法,嚴重影響軟體開發維護的成本。

例如以下是新的ES6物件導向繼承寫法(假設類別Shape已經定義過了):

class Rectangle extends Shape {
  constructor (id, x, y, width, height) {
    super(id, x, y);
    this.width  = width;
    this.height = height;
  }
}

class Circle extends Shape {
  constructor (id, x, y, radius) {
    super(id, x, y);
    this.radius = radius;
  }
}

以下則是ES5的繼承寫法:

var Rectangle = function (id, x, y, width, height) {
  Shape.call(this, id, x, y);
  this.width  = width;
  this.height = height;
};
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var Circle = function (id, x, y, radius) {
  Shape.call(this, id, x, y);
  this.radius = radius;
};
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;

嚴格來說,JavaScript ES6的物件導向程式設計,骨子裡還是prototype-based,與C++、Java這些語言的物件導向運作原理不同。但對大部份開發人員來說,可能察覺不到這種差異,因為ES6的物件導向語法與傳統語言長得很像。ES6物件導向語法的設計,採用了所謂「maximally minimal」的原則,也就是儘量縮小這次改變的範圍。至於日後ES7是否會為JavaScript注入更多物件導向特性,還有待觀察,因為不少人持反對意見。

另外要注意的是,以上我雖然舉了類別繼承的例子,其實很多時候採用物件複合才是更恰當的做法,也就是所謂的「favor composition over inheritance」。關於這個問題,可以參考我前幾天翻譯的文章:物件導向程式設計:為何說composition優於inheritance?

Promises

JavaScript是一種單一執行緒、以事件為基礎(event-based)的非同步程式語言。對於習慣用同步程式語言(synchronous programming language)寫程式的開發者,JavaScript的單一執行緒似乎是件奇怪的事。但當你掌握訣竅之後,你會發現event-based的JavaScript處理各種非同步事件,其實比起傳統synchronous multithread programming更有效率。

不過這是有代價的。JavaScript的callback對人類較不自然,比循序的程式難閱讀,callback多層之後產生的「callback hell」,更是JavaScript開發人員的惡夢!維護這種程式既花力氣,又容易出錯。

Callback hell大概長這樣:(實際上更難看,因為還有錯誤處理的程式碼,而且callback function可能定義在其它地方)

getData(function (a) {
  getMoreData(a, function (b) {
    getMoreData(b, function (c) {
      getMoreData(c, function (d) {
        getMoreData(d, function (e) {
          ...
        });
      });
    });
  });
});

生命會尋找出路。有人開始把concurrent programming裡的promise搬過來用,將層層callback轉為一連串簡單的循序呼叫,幫助JavaScript開發人員遠離callback hell惡夢。在ES5時期,就已經有很多第三方Promise程式庫了,ES6則進一步把Promise納入標準,成為JavaScript的標準物件。

Generators

我第一次接觸generator這個觀念,是2002年左右在Python 2.2看到的,那時我已經用Python加C++ extension寫大型Python程式好幾年了(從Python 1.5開始)。對於習慣synchronous programming的我,第一次看到generator真的很陌生:函式執行到一半可以先返回,下一次被呼叫再接著往下執行。我到後來才慢慢搞清楚這件事。

ES6的generator除了作為iterator,另一個重要用途是把yield當作呼叫非同步函式的關鍵字。搭配像co這樣的程式庫,在JavaScript裡處理非同步呼叫(例如呼叫外部伺服器API、存取資料庫、操作cache layer等等)變得異常簡單。Promise已經大大簡化了非同步程式的開發,generator(加上co)則進一步把它變成一行簡單的函式呼叫。在ES7的async/await到來之前,generator是ES6送給JavaScript開發者的大禮,也是我最欣賞的另一個ES6特色。

其它ES6重要特性

以上4個是我認為最重大的改變。當然還有一些ES6特性也很重要,例如:

  • const/let:有了constlet,JavaScript開發者應該把var收到抽屜裡了。var有許多奇怪特性,let則文明得多。此外,能用const就用const,你會發現程式碼可讀性大大提高。(別小看這件事,一旦你知道一個變數是const,它在你讀程式過程的cognitive load就會輕很多,能幫助你大腦騰出更多空間裝其它東西。)
  • string interpolation:有了string interpolation語法,從此少掉一堆字串相加的算式,讓字串歸字串,程式歸程式。
  • module import/export:模組的定義和引用,終於成為JavaScript標準的一部份,從此不再需要用require作為替代方案。
  • 其它:for-of,property shorthand,array matching,object matching,Object.assign……等等。

二、關於JavaScript專案開發

對JavaScript語言的掌握

和其它語言相比,JavaScript語言有一些較特殊的地方,例如:

  • var scope/hoisting
  • closure
  • this
  • strict mode
  • ==/===的差異

……等等。這裡面有許多細節(甚至地雷),JavaScript開發者對這些應該要清楚掌握,以免不小心犯錯,造成程式bug。

Node.js與npm

2009年,Ryan Dahl以Google的V8 JavaScript引擎為基礎,加上event loop及一套非同步I/O程式庫,創造出Node.js。去掉對瀏覽器的依賴,Node.js成為可以在Linux環境下獨立執行的JavaScript直譯器,解開封印,為JavaScript開啟了一個全新時代。(第一個Windows版本的Node.js,則是2011年由Joyent與Microsoft共同完成。)Node.js雖然基於Google V8 JavaScript引擎,卻有它自己的執行環境與程式庫(例如EventEmitterStreamhttp.Server等等),與瀏覽器端的JavaScript行為有諸多不同之處。

2011年,Node.js的套件管理員(package manager)npm推出,為Node.js開發者提供極大方便,也使JavaScript套件的協作、共享變得非常容易。npm是JavaScript專案開發不可或缺的工具,搭配簡單的shell command,在不使用Grunt、Gulp等建置工具的情況下,npm甚至足以處理許多專案的建置與部署需求。事實上,有不少人(包括我在內)認為用npm就可以了,例如這篇文章:Why I Left Gulp and Grunt for npm Scripts

ES6轉譯

由於使用者端的瀏覽器並不一定支援ES6,將ES6程式碼轉為瀏覽器能執行的ES5,就成了部署JavaScript程式碼的標準程序之一。過去這種轉譯工具(transpiler)有很多,例如6to5、Google traceur等等。自從6to5改名為Babel之後,Babel似乎就成為大家的標準工具了。

除了將ES6程式碼轉換為ES5,Babel還能處理更多的事,例如轉換React JSX語法、移除Flow型別註記、甚至支援ES7 async/await等等。

程式碼檢查工具

JavaScript是一種動態程式語言(dynamic programming language)。對於習慣靜態程式語言(static programming language,例如C/C++、Java)的開發者而言,動態程式語言一開始很方便。不用事先宣告變數型別,太棒了;不用compile就能執行,多好!也不會有compile error了。不過很快你就會發現,這只不過是把bug留到runtime再爆開來而已,bug並沒有自動消失。相反的,runtime才出現的bug,代價比compile-time要高很多,很可能從5分鐘變5小時、甚至5天。

修復bug的成本,由小到大依序是:compile-time,testing,production runtime。對於沒有compile-time的動態程式語言來說,如何避免在runtime才發現bug,就成為一個重大課題。(當然靜態程式語言也是,但它們通常在compile-time就能找出許多bug,也有更多程式碼檢查工具可供選擇。)

在JavaScript開發社群中,使用linter(一種靜態檢查程式碼錯誤的工具)的做法非常普遍。早期被普遍使用的JavaScript linter,是Douglas Crockford寫的JSLint。後來JSHint出現,變得很受歡迎。ESLint則是Nicholas Zakas開發的另一種JavaScript linter,也是我個人目前在用的。無論哪一種,專案團隊都應該採用linter檢查程式碼,並藉此統一JavaScript程式碼的coding style。

型別宣告及檢查

缺少型別宣告(type declaration)及compile-time型別檢查,是動態程式語言的另一項弱點。在JavaScript開發社群中,解決這個問題的常見選擇有兩種:TypeScriptFlow。TypeScript是微軟開發的JavaScript擴充語言,有方便的IDE環境,並且對Windows開發環境有很好的支援;但若你的專案原本沒有採用TypeScript,想要漸進導入,可能會面臨一些挑戰。Flow是Facebook開發的JavaScript型別檢查工具,採用與TypeScript類似的型別註記語法;因為Flow可以自動分析既有程式碼的型別,有助於專案漸進導入型別註記,但對Windows開發環境的支援度則較弱。Flow比較類似「非侵入性」的解決方案,如果開發環境允許,我個人推薦Flow。

單元測試

動態程式語言比起靜態程式語言更需要單元測試(unit test)。JavaScript的單元測試框架有很多種,早期被廣泛使用的是JasmineMocha。近年則有許多人提倡回歸到更簡單、更模組化的單元測試框架(例如這篇文章),包括TapeAVA等;其中由Tape演變而來、加入平行執行能力(因此能大幅縮短單元測試執行時間)的AVA,特別引起JavaScript開發社群的注意,短短一年多,已在GitHub上獲得大量關注。


The young AVA already received ~5,000 stars!

單元測試不是免費午餐。測試程式碼一樣要花人力撰寫維護,因此它的品質也很重要。根據我過去的工作經驗,測試程式碼的品質通常較差,這可能是因為很多人(尤其主管或PM)只看到test coverage數字,這部份需要注意。

了解你用的每樣東西

由於npm及GitHub帶來的便利,世界上已經有超過20幾萬種JavaScript套件與各種工具。這雖然給JavaScript開發者更多選擇,卻也常導致JavaScript專案有過多的、不必要的外部依賴關係(external dependency)。開發團隊應該謹慎評估每一個要使用的套件與工具,確實了解它們的用途及必要性,而非盲目引入,以免造成日後專案維護的負擔。例如:你用了建置工具Gulp,原始碼就有了Gulp code,團隊成員要學習Gulp;你用的每個Gulp plugin,在日後開發環境版本更新時有可能壞掉,如果plugin作者不再維護更新、或更新速度較慢,你就得自己跳進去維護;等等。任何工具都有它的用途,能解決某些問題,但我們也要注意它的總體擁有成本(Total Cost of Ownership,TCO)。

結語

JavaScript是普及率最高的程式語言之一,很多開發者對它都不陌生,這使得JavaScript進入門檻相對較低。另一方面,如果團隊採用JavaScript作為開發的主要語言(包括伺服器端),還可以統一前後端語言,在團隊專業經驗培養上具備更大優勢。

今天的JavaScript早已不是當年那個陽春的小玩意兒,而是有著豐富成熟語法、大量第三方程式庫支援,令人寫起來愉快的現代化語言。許多知名公司包括:LinkedIn、Netflix、New York Times、PayPal、Uber、Yahoo等,早已使用Node.js/JavaScript開發產品多年。充份運用JavaScript語言的最新特性,輔以適當的工具與流程,JavaScript將會是一個高效能、高生產力的開發利器。

限於篇幅,以上只是簡單介紹,希望這篇文章,能幫助大家對現代JavaScript語言有一個初步認識!

(本篇是我為Arthur's Coding Workshop的「JavaScript程式設計進階」課程所寫的課前導讀文章,課程內容涵蓋以上各主題。)

8筆討論 回應文章

請教一下,如果使用硬體裝置,透過其本身的wifi或者bt傳送硬體的控制數據到server,使用JavaScript可以建構這種server嗎?

如果是透過wifi送一些http request,對Node.js server而言就跟接收一般瀏覽器的http reqest一樣,所以應該沒問題。透過bt我就不清楚了,感覺上複雜很多。

所以webduino(https://webduino.io/)這家公司有可能是用JavaScript來寫server的,看他們介紹是用HTML和JavaScript來寫程式,即可從遠端控制 Arduino devices with WiFi。

是的,他們的程式碼有瀏覽器端與Node.js端:https://github.com/webduinoio/webduino-js

話說這家公司還真有趣,是高雄的公司呢!如果他們台北有開課,小孩大一點說不定我也會讓他去上。 :-)

上個月在台北maker faire現場我有和他們聊,是一群學校剛畢業的年輕人創辦的新創公司,真是不簡單。他們說公司的主力不是做硬體,而是將主攻iot cloud server。這一點觸發我對iot的server的興趣,而剛好你寫了這篇介紹,使我從一竅不通到略知一二,真巧。

Allen C5年(更新)

Arthur Liao 先生,您好!
我是科技報橘TechOrange(https://buzzorange.com/techorange)的編輯,張瑋倫

不好意思打擾!

看到您分享的文章〈JavaScript傳奇:從跑龍套到挑大樑的程式語言〉,我們認為非常適合我們在科技報橘中想和讀者分享的內容,也希望讓更多讀者看見您的好文,因此冒昧來信想請問,是否能讓我們轉載這篇文章呢?

若有榮幸能轉載這篇文章,我們會清楚註明作者,並附上來源,讓有興趣的讀者能夠回訪。

另外,我們也許會依社群讀者的喜好,擬出一個不離原意的標題。若是您有期望的標題希望我們直接使用,也可以先行告訴我們,轉載的規範都會依照您的需求調整,並以您的意願為優先!

若能徵求到您的同意,我們也會在轉載完後,提供連結回覆給您,以便您有任何的建議可以隨時和我們做調整或修改。

再麻煩您撥冗評估,並回覆我們了!
在此說聲感謝

TechOrange報橘
採訪編輯 張瑋倫 

歡迎轉載,標題你們可以自訂。謝謝!

謝謝您的回覆! 待文章刊載後,會再將連結回覆給您! 感謝您!