JavaScript的死與生
JavaScript 很流行,但它有先天缺陷。Brendan Eich 當初只花了10 天時間就把 JavaScript 設(shè)計出來了,作為 JavaScript 之父,BE 如是說:
與其說我愛 JavaScript,不如說我恨它。它是 C 語言和 Self 語言一夜情的產(chǎn)物。十八世紀英國文學家約翰遜博士說得好:“它的優(yōu)秀之處并非原創(chuàng),它的原創(chuàng)之處并不優(yōu)秀。”
(摘選自阮一峰的翻譯:JavaScript 誕生記)
JavaScript 的不足,最明顯之處是語法。
糟糕冗長的語法
可選參數(shù)和默認值
- function(a, b, option) {
- option = option || {};
- // ...
- }
上面的代碼中,option 是可選參數(shù),當沒有傳遞時,默認值是 {}. 然而,傳遞的 option 值有可能是假值(falsy 值)。嚴格來寫,得如下判斷:
- function(a, b, option) {
- option = arguments.length > 2 ? option : {};
- // ...
- }
注意:option = typeof option !== 'undefined' ? option : {} 也有可能是錯誤的,因為傳遞過來的可能就是undefined.
當不需要 b 參數(shù),刪除后,基于 arguments.length 的判斷很容易導致忘記修改而出錯:
- function(a, option) {
- option = arguments.length > 2 ? option : {};
- // ...
- }
如果能增加以下語法該多好呀:
- function(a, b, option = {}) {
- // ...
- }
Let
閉包很強大,也很惱火:
- for (var i=0, ilen=elements.length; i
- var element = elements[i];
- LIB_addEventListener(element, 'click', function(event) {
- alert('I was originally number ' + i);
- });
- }
上面的代碼經(jīng)常在面試題中出現(xiàn),解決辦法是再包裹一層:
- for (var i=0, ilen=elements.length; i
- var element = elements[i];
- (function(num) {
- LIB_addEventListener(element, 'click', function(event) {
- alert('I was originally number ' + num);
- });
- }(i));
- }
如果直接支持 let 語法該多好呀:
- for (var i=0, ilen=elements.length; i
- var element = elements[i];
- let (num = i) {
- LIB_addEventListener(element, function(event) {
- alert('I was originally number ' + num);
- });
- };
- }
模塊
模塊模式是一種無奈的選擇:
- var event = (function() {
- // private variables
- var listeners = [];
- function addEventListener(f) {
- listeners.push(f);
- }
- function clearEventListeners() {
- listeners = [];
- }
- // ...
- // export the module's API
- return {
- addEventListener: addEventListener,
- clearEventListeners: clearEventListeners
- // ...
- };
- }());
如果原生支持該多好呀:
- module event {
- // private variables
- var listeners = [];
- export function addEventListener(f) {
- listeners.push(f);
- }
- export function clearEventListeners() {
- listeners = [];
- }
- // ...
- }
- (function() {
- import event;
- // ...
- }());
繼承
JavaScript 要通過原型鏈來實現(xiàn)繼承:
- function Employee(first, last, position) {
- // call the superclass constructor
- Person.call(this, first, last);
- this.position = position;
- };
- // inherit from Person
- Employee.prototype = Object.create(Person.prototype);
- Employee.prototype.constructor = Employee;
- // define an overridding toString() method
- Employee.prototype.toString = function() {
- // call superclass's overridden toString() method
- return Person.prototype.toString.call(this) +
- ' is a ' + this.position;
- };
如果能寫成下面這樣該多好呀:
- class Employee extends Person {
- constructor(first, last, position) {
- super(first, last);
- public position = position;
- }
- update(camera) {
- return super.update() + ' is a ' + position;
- }
- }
感悟
ECMAScript 委員會已意識到 JavaScript 在語法層面上的不足。在 Harmony 規(guī)范中,以上所有語法均已提案。
我們什么時候才能使用以上語法呢?
只要有宏(Macro)
Lisp 語言的宏特性非常強大。通過宏,你可以根據(jù)自己的喜好定義想要的語法格式。宏特性使得 Lisp 成為一門“可編程的編程語言(the programmable programming language)”.
JavaScript 沒有宏。給類 C 語言添加宏特性,目前依舊是個研究課題,很有難度。
只要有宏,我們就可以自定義語法。但 JavaScript 的宏特性遙遙無期,還是找找其他路子吧。
Harmony
Harmony 規(guī)范里的語法擴展,可能是我們所有人的夢。Harmony 有可能成為 ECMAScript 6 規(guī)范。在這之前,我們需要等待,耐心等待。
截止 2011 年 5 月,w3school 顯示 IE6 的市場份額還有 2.4%. Net Market Share 顯示 IE6 占有 10.36% 市場份額。還有 IE7 的市場份額也不少。這些老舊瀏覽器短期內(nèi)不會退隱市場,對于商業(yè)公司來說,比如 Amazon,不可能放棄這批用戶。糟糕的現(xiàn)狀。(中國大陸更慘,IE6/7 還占有 40% 多市場份額)
我們不能寄期望于“IE 該死”這類呼吁來讓用戶升級。聽到過一種說法:IE 用戶僅會在更換電腦硬件時,才升級瀏覽器。悲催的是,對于普通用戶來說,收收 email, 上上 Facebook, Twitter, 現(xiàn)有的硬件已足夠。沒有理由讓他們?nèi)セㄒ还P錢。
Goggle Apps 最近宣布,從 2011 年 8 月開始,將停止支持 IE7.
通過各種保守估計,Amazon 的網(wǎng)站開發(fā)者,用上 Harmony 語法擴展,要一直等到 2023 年!
風華正茂的你,愿意等待 10 多年后,再用上這些好用的語法嗎?
#p#
JavaScript 已死
死因:分號癌。(semicolon cancer. 作者的調(diào)侃,意指語法導致 JavaScript 死去)
通過上面的分析可以看出,宏特性實現(xiàn)太難,Harmony 規(guī)范的實現(xiàn)則遙遙無期。大量程序員開始書寫 JavaScript, 其中有很多人已經(jīng)厭倦或開始厭倦 JavaScript 冗長糟糕的語法。我們需要新的語法,我們不想等待!JavaScript,作為源碼編寫語言,已經(jīng)死了!
JavaScript 先生,你曾有過輝煌的統(tǒng)治。我們與你,有過甜蜜的回憶,一起產(chǎn)出過很多有趣的應(yīng)用。祝福逝者安息。
JavaScript 長存
程序員喜歡掌控自己的命運。作為源碼編寫語言,JavaScript 已死。我們可以選擇或創(chuàng)造另一種更好的源碼語言,將其編譯成 ECMAScript 3 的語法格式。
JavaScript 的新生,是作為編譯目標(compilation target)。
編譯成 JavaScript 的語言
能編譯成 JavaScript 的語言有很多。我在 1997 年時,收集過一份列表。包括:
JavaScript 擴展語言:已死的 ECMAScript 4, Narrative Script, Objective-J.
已存在的語言:Scheme, Common Lisp, Smalltalk, Ruby, Python, Java, C#, Haskell 等。
還有一些嶄新的語言:HaXe, Milescript, Links, Flapjax, 專門為 web 編程而設(shè)計。
在這些編譯器項目中,Goggle 的 GWT Java-to-JavaScript 編譯器有可能是最成功的一個。 然而悲劇的是,現(xiàn)實項目中,很少看到 GWT 的身影。原因如下:
1. 維護成本很高。編譯器可能有 bug. 假設(shè)你在一個大型項目中,發(fā)現(xiàn)了編譯器的一個 bug, 作為維護者,除了維護源碼,你還得維護編譯器。天哪,你有這個本事嗎?你有這個本事,CEO 也不愿意花這個錢呀。
2. 調(diào)試麻煩。Firebug 報了一個錯,報的是編譯后的行號。老板站在你背后:趕快啦,小伙子!可是這該死的編譯后代碼,究竟對應(yīng)哪一行源碼呀?
3. 招聘不到人。假設(shè)你使用 Objective-J 開發(fā)一個項目,但人手不夠。趕緊招人,HR 說 1000 個人里面,只有 100 個聽說過 Objective-J, 另外 900 個只聽說過 JavaScript. 結(jié)局是你每找一個新人,都得先培訓一把,真是糟糕透頂。
雖然編譯器有以上各種不是,但各種編譯器依舊如雨后春筍大量涌現(xiàn)。毫無疑問,編寫 JavaScript 編譯器非??帷=o我報酬,我也想寫一個。
在上面的編譯器列表中,有一個非常有名的引起過很大轟動的:CoffeeScript. 我們來談?wù)勊?/p>
CoffeeScript
為什么 CoffeeScript 如此火爆?我到現(xiàn)在為止也沒想明白。是因為給空白賦予了意義,還是帶箭頭的函數(shù)語法?每念及此,我的胃就忍不住波濤洶涌。CoffeeScript 有很多新特性:default parameter values, rest parameters, spread, destructuring,fixing the whole implied global mess… CoffeeScript 很多特性是 Harmony 規(guī)范的一部分,有可能在未來瀏覽器中直接支持。CoffeeScript 能讓人立刻滿足。
@pyronicide 在 Twitter 上說:#coffeescript 支持函數(shù)默認參數(shù)值,這太令人興奮了。
在 TXJS 2011 大會上,Douglas Crockford 也表示:CoffeeScript 無疑是個好東東。
CoffeeScript: Accelerated JavaScript Development 一書的作者說:
@trevorburnham
[...] CoffeeScript 不是將 JS 變成 Ruby 或 Python, 而是通過一套語法,來更好地發(fā)揮 JavaScript 內(nèi)在的優(yōu)秀。
Douglas Crockford 認為 JavaScript 有好的方面,并開發(fā)了 JSLint 工具來保證開發(fā)者遠離 JavaScript 中的糟粕。JSLint 允許的語法子集值得擁有自己的名字,我們不妨稱之為 GoodScript.
ECMAScript 5 則引入了 "use strict" 指令來限制 with 等語法的使用。
CoffeeScript, GoodScript, ECMAScript 5 的目標是一致的:遠離糟粕,同時提供有用的、安全的語言特性給你。
GoodScript 沒有提供新特性,ECMAScript 5 的嚴格模式,大部分瀏覽器還不支持。然而,我們不想等待。
剩下的選擇是 CoffeeScript. 好處:
特別適合 web 開發(fā)。這可能是其他 JavaScript 編譯器沒做或做得不好的地方。
CoffeeScript 對 JavaScript 的封裝適度。這樣能使得編譯后的代碼比較容易閱讀,調(diào)試也就不那么困難了。
CoffeeScript 看起來就像是書寫 JavaScript 代碼的一套宏。
CoffeeScript 的編譯器提供客戶端版本。這樣,使用者可以自由選擇,開發(fā)者也可以快速開發(fā)新功能,而不受標準的局限。由社區(qū)的愿景和需求推動 CoffeeScript 的發(fā)展,這很不錯。
發(fā)明自己的語言
你可以去做,這會是一個很好的練習。作為 JavaScript 編譯器的開發(fā)者,將擁有無上榮耀。
發(fā)明自己的語言,危險之處在于:你認為最終你將比 JavaScript 做得更好。語言設(shè)計很難,我敢打賭你的語言很難擴大市場份額。CoffeeScript 尚未進入青春期,就已經(jīng)有抱怨的聲音了。
你可能會為自己的編譯器能編譯出簡單、可讀的代碼而驕傲??墒牵慌龅教厥馇闆r,你就會郁悶得想撞墻。
你的語言里將會出現(xiàn)慣用法。接著,你馬上會發(fā)現(xiàn)有人會破壞這些慣用法(除非你的語言剛好支持宏)。
風涼話就不多說了。立刻去開發(fā)自己的語言吧,你會成為一個很好的程序員。
作為編譯目標語言,JavaScript 缺少什么?
作為編譯目標語言,JavaScript 重獲新生。在 JSConf.US talk 中,Brendan Eich 表示:Harmony 規(guī)范的目的是讓 JavaScript 成為更好的編譯目標。
編譯后的 JavaScript 有可能比手寫的 JavaScript 運行效率更低,這就和編譯后的 C 有可能比手寫的匯編語言效率更低一樣。幸運的是,JavaScript 的瓶頸主要在 DOM 操作上,語言本身的效率損耗相對可以接受。雖然話是這么說,但一些高效的源碼語言編譯后,由于 JavaScript 本身的問題,可能極其低效,以致于無法在真實環(huán)境中使用。Harmony 規(guī)范中已經(jīng)有部分特性能保證避免這類問題。
合理的尾部調(diào)用
- function isEven(number) {
- if (number === 0) {
- return true;
- }
- else {
- return isOdd(number - 1);
- }
- }
- function isOdd(number) {
- if (number === 0) {
- return false;
- }
- else {
- return isEven(number - 1);
- }
- }
- isEven(100000); // InternalError: too much recursion
上面的代碼,在目前的瀏覽器中運行,會堆棧溢出。
可以通過 蹦床(trampolines) 技巧來優(yōu)化:
- function bounce(ret) {
- while (typeof ret === 'function') {
- ret = ret();
- }
- return ret;
- }
- function isEven(number) {
- if (number === 0) {
- return true;
- }
- else {
- return function() {
- return isOdd(number - 1);
- };
- }
- }
- function isOdd(number) {
- if (number === 0) {
- return false;
- }
- else {
- return function() {
- return isEven(number - 1);
- };
- }
- }
- bounce(function() {return isEven(100000);}); // true
通過 bounce 方式,在運行 isOdd(99999) 時,isEven(100000) 已經(jīng)完成并從堆棧中退出了,因此不會造成溢出。
幸運地是,ECMAScript Harmony 已經(jīng)考慮到了這一點,會自動進行優(yōu)化。這對程序開發(fā)者和編譯器開發(fā)者都是有益的。
Lambdas
lambda 并不神奇。簡言之,lambda 就是可調(diào)用的東西,比如 function, 但需要遵守 TCP(Tennent 一致性原則,Tennent’s Correspondence Principle)。TCP 要求:用一個緊鄰的 lambda 對表達式或代碼塊進行封裝,不會改變被封裝的代碼的含義。
很顯然,JavaScript 的 function 不是 lambda:
- function one() {
- return 1;
- }
- one(); // 1
封裝后,返回值發(fā)生了變化:
- function one() {
- (function() {
- return 1;
- }());
- }
- one(); // undefined
對于接受兩個參數(shù)并將其求和的代碼塊,lambda 語法提議寫成:{|a, b| a + b}
對于上面的例子,采用 lambda 封裝將保證返回值和封裝前一樣:
- function one() {
- ({||
- return 1;
- }());
- }
- one(); // 1
lambda 塊的稻草人提案目前還沒有提升到 Harmony 規(guī)范中,讓我們一起努力吧。
瀏覽器缺少什么?
JavaScript 的興衰存亡離不開瀏覽器。JavaScript 的新生,也需要瀏覽器的靠譜支持。
Mozilla 發(fā)起了一個 SourceMap 項目,這可以使得在調(diào)試編譯后的代碼時,能映射回源碼的對應(yīng)代碼行。這太 cool 了,能極大的減少調(diào)試成本。
聽說 Webkit 的小伙子們也在干同樣的事情,可惜我找不到任何證據(jù)了-.-
通曉數(shù)種語言
JavaScript 在瀏覽器上的壟斷,意味著前端程序員都會同一門語言。然而,編譯器的差異性,會使得 CoffeeScript 程序員,很難立刻看懂基于 Traceur 的 JavaScript 代碼。
這種分歧不可避免。比如有 C, 同時有 C++ 和 Objective-C 等各種語言。Java 也一樣,基于 JVM 還可以選擇 Clojure 或 JRuby. 微軟意識到這一點,開發(fā)了 CLR. C#, Basic, IronPython 等都可以運行在 CLR 上。
前端中的溝通障礙并非新鮮事物。一個 Dojo 程序員,難以立刻明白基于 jQuery 或 YUI 的代碼。
擁有多種源碼書寫語言會增加社區(qū)的溝通障礙。程序員仍需要了解 JavaScript. 至少一段時間內(nèi)程序員還需要懂得 JavaScript. 但在短短幾年后,他們可能會更了解其他源碼語言。
總結(jié)
能有機會目睹 JavaScript 的新生,是件很棒的事情。在 JavaScript 編譯的競爭中,很難說誰會最終贏得市場份額,但毫無疑問,這肯定會很有趣。如今,CoffeeScript 蓄勢待發(fā),但我相信許多其他成功的源碼語言將接踵而至。
你的想法呢?
原文鏈接:http://www.starming.com/index.php?action=plugin&v=wave&tpl=union&ac=viewgrouppost&gid=119&tid=1000002685
【編輯推薦】