JavaScript的死與生

文章內容如下:


JavaScript的成功得益於在正確的時間出現在正確的地點。JavaScript的興起與瀏覽器的支持息息相關。你瞧,VBScript就沒這麼好運氣。


JavaScript很流行,但它有先天缺陷。Brendan Eich當初隻花瞭10天時間就把JavaScript設計出來瞭,作為JavaScript之父Brendan Eich如是說:


與其說我愛JavaScript,不如說我恨它。它是C語言和Self語言一夜情的產物。十八世紀英國文學傢約翰遜博士說得好:“它的優秀之處並非原創,它的原創之處並不優秀。”
JavaScript的不足,最明顯之處是語法。


糟糕冗長的語法 可選參數和默認值


function(a, b, option) { 
  optionoption = option || {}; 
  // … 
}
上面的代碼中,option是可選參數,當沒有傳遞時,默認值是{}.然而,傳遞的option值有可能是假值(falsy值)。嚴格來寫,得如下判斷:


function(a, b, option) { 
  option = arguments.length > 2 ? option : {}; 
  // … 
}
註意:option = typeof option !== undefined ? option :{}也有可能是錯誤的,因為傳遞過來的可能就是undefined。當不需要b參數,刪除後,基於arguments.length 的判斷很容易導致忘記修改而出錯:


function(a, option) { 
  option = arguments.length > 2 ? option : {}; 
  // … 
}
如果能增加以下語法該多好呀:


function(a, b, option = {}) { 
  // … 
}
Let
閉包很強大,也很惱火:


for (var i=0, ilen=elements.length; i<ilen; i++) { 
  var element = elements[i]; 
  LIB_addEventListener(element, click, function(event) { 
    alert(I was originally number + i); 
  }); 
}
上面的代碼經常在面試題中出現,解決辦法是再包裹一層:


for (var i=0, ilen=elements.length; i<ilen; 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<ilen; i++) {
  var element = elements[i];
  let (num = i) {
    LIB_addEventListener(element, function(event) {
      alert(I was originally number + num);
    });
  };
}
模塊
模塊模式是一種無奈的選擇:


如果原生支持該多好呀:


module event { 
 
  // private variables 
  var listeners = []; 
 
  export function addEventListener(f) { 
    listeners.push(f); 
  } 
 
  export function clearEventListeners() { 
     listeners = []; 
  } 
 
  // … 

 
(function() { 
 
  import event; 
 
  // … 
}());
繼承
JavaScript要通過原型鏈來實現繼承:


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); 
EmployeeEmployee.prototype.constructor = Employee; 
 
// define an overridding toString() method 
Employee.prototype.toString = function() { 
  // call superclasss 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 positionposition = position; 
  } 
 
  update(camera) { 
      return super.update() + is a + position; 
  } 
}
感悟
ECMAScript委員會已意識到JavaScript在語法層面上的不足。在Harmony規范中,以上所有語法均已提案。


我們什麼時候才能使用以上語法呢?


隻要有宏(Macro)
Lisp語言的宏特性非常強大。通過宏,你可以根據自己的喜好定義想要的語法格式。宏特性使得Lisp成為一門“可編程的編程語言(the programmable programming language)”.


JavaScript沒有宏。給類C語言添加宏特性,目前依舊是個研究課題,很有難度。


隻要有宏,我們就可以自定義語法。但JavaScript的宏特性遙遙無期,還是找找其他路子吧。


Harmony
Harmony規范裡的語法擴展,可能是我們所有人的夢。Harmony有可能成為ECMAScript 6規范。在這之前,我們需要等待,耐心等待。


截止2011年5月,w3school顯示IE6的市場份額還有2.4%。Net Market Share 顯示IE6占有10.36%市場份額。還有IE7的市場份額也不少。這些老舊瀏覽器短期內不會退隱市場,對於商業公司來說,比如Amazon,不可能放棄這批用戶。糟糕的現狀。(中國大陸更慘,IE6/7還占有40%多市場份額)


我們不能寄期望於“IE該死”這類呼籲來讓用戶升級。聽到過一種說法:IE用戶僅會在更換電腦硬件時,才升級瀏覽器。悲催的是,對於普通用戶來說,收email,上Facebook,Twitter,現有的硬件已足夠。沒有理由讓他們去花一筆錢。


Goggle Apps最近宣佈,從2011年8月開始,將停止支持IE7.


通過各種保守估計,Amazon的網站開發者,用上Harmony語法擴展,要一直等到2023年!


風華正茂的你,願意等待10多年後,再用上這些好用的語法嗎?


JavaScript已死
死因:分號癌。(semicolon cancer.作者的調侃,意指語法導致JavaScript死去)


通過上面的分析可以看出,宏特性實現太難,Harmony規范的實現則遙遙無期。大量程序員開始書寫JavaScript,其中有很多人已經厭倦或開始厭倦JavaScript冗長糟糕的語法。我們需要新的語法,我們不想等待!JavaScript,作為源碼編寫語言,已經死瞭!


JavaScript先生,你曾有過輝煌的統治。我們與你,有過甜蜜的回憶,一起產出過很多有趣的應用。祝福逝者安息。


JavaScript長存
程序員喜歡掌控自己的命運。作為源碼編寫語言,JavaScript已死。我們可以選擇或創造另一種更好的源碼語言,將其編譯成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 編程而設計。
在這些編譯器項目中,Goggle的GWT Java-to-JavaScript編譯器有可能是最成功的一個。然而悲劇的是,現實項目中,很少看到GWT的身影。原因如下:


1.維護成本很高。編譯器可能有bug.假設你在一個大型項目中,發現瞭編譯器的一個bug,作為維護者,除瞭維護源碼,你還得維護編譯器。天哪,你有這個本事嗎?你有這個本事,CEO也不願意花這個錢呀。


2.調試麻煩。Firebug報瞭一個錯,報的是編譯後的行號。老板站在你背後:趕快啦,小夥子!可是這該死的編譯後代碼,究竟對應哪一行源碼呀?


3.招聘不到人。假設你使用Objective-J開發一個項目,但人手不夠。趕緊招人,HR說1000個人裡面,隻有100個聽說過Objective-J,另外900個隻聽說過JavaScript.結局是你每找一個新人,都得先培訓一把,真是糟糕透頂。


雖然編譯器有以上各種不是,但各種編譯器依舊如雨後春筍大量湧現。毫無疑問,編寫JavaScript編譯器非常酷。給我報酬,我也想寫一個。


在上面的編譯器列表中,有一個非常有名的引起過很大轟動的:CoffeeScript。我們來談談它。
CoffeeScript
為什麼CoffeeScript如此火爆?我到現在為止也沒想明白。是因為給空白賦予瞭意義,還是帶箭頭的函數語法?每念及此,我的胃就忍不住波濤洶湧。CoffeeScript有很多新特性:default parameter values, rest parameters, spread, destructuring,fixing the whole implied global mess… CoffeeScript很多特性是Harmony規范的一部分,有可能在未來瀏覽器中直接支持。CoffeeScript 能讓人立刻滿足。


@pyronicide在Twitter上說:#coffeescript 支持函數默認參數值,這太令人興奮瞭。
在TXJS 2011大會上,Douglas Crockford 也表示:CoffeeScript無疑是個好東東。


CoffeeScript: Accelerated JavaScript Development 一書的作者說:


@trevorburnham
[…] CoffeeScript 不是將 JS 變成 Ruby 或 Python,而是通過一套語法,來更好地發揮JavaScript內在的優秀。



Douglas Crockford 認為 JavaScript 有好的方面,並開發瞭JSLint工具來保證開發者遠離JavaScript中的糟粕。JSLint允許的語法子集值得擁有自己的名字,我們不妨稱之為GoodScript.


ECMAScript 5則引入瞭 “use strict” 指令來限制with等語法的使用。


CoffeeScript, GoodScript, ECMAScript 5的目標是一致的:遠離糟粕,同時提供有用的、安全的語言特性給你。


GoodScript沒有提供新特性,ECMAScript 5的嚴格模式,大部分瀏覽器還不支持。然而,我們不想等待。


剩下的選擇是CoffeeScript。好處:


特別適合web開發。這可能是其他JavaScript編譯器沒做或做得不好的地方。
CoffeeScript對JavaScript的封裝適度。這樣能使得編譯後的代碼比較容易閱讀,調試也就不那麼困難瞭。
CoffeeScript看起來就像是書寫JavaScript代碼的一套宏。


CoffeeScript的編譯器提供客戶端版本。這樣,使用者可以自由選擇,開發者也可以快速開發新功能,而不受標準的局限。由社區的願景和需求推動CoffeeScript的發展,這很不錯。


發明自己的語言
你可以去做,這會是一個很好的練習。作為JavaScript編譯器的開發者,將擁有無上榮耀。


發明自己的語言,危險之處在於:你認為最終你將比JavaScript做得更好。語言設計很難,我敢打賭你的語言很難擴大市場份額。CoffeeScript尚未進入青春期,就已經有抱怨的聲音瞭。


你可能會為自己的編譯器能編譯出簡單、可讀的代碼而驕傲。可是,一碰到特殊情況,你就會鬱悶得想撞墻。


你的語言裡將會出現慣用法。接著,你馬上會發現有人會破壞這些慣用法(除非你的語言剛好支持宏)。


風涼話就不多說瞭。立刻去開發自己的語言吧,你會成為一個很好的程序員。


作為編譯目標語言,JavaScript缺少什麼?
作為編譯目標語言,JavaScript重獲新生。在 JSConf.US talk中,Brendan Eich表示:Harmony規范的目的是讓JavaScript成為更好的編譯目標。


編譯後的JavaScript有可能比手寫的JavaScript運行效率更低,這就和編譯後的C有可能比手寫的匯編語言效率更低一樣。幸運的是,JavaScript的瓶頸主要在DOM操作上,語言本身的效率損耗相對可以接受。雖然話是這麼說,但一些高效的源碼語言編譯後,由於JavaScript本身的問題,可能極其低效,以致於無法在真實環境中使用。Harmony規范中已經有部分特性能保證避免這類問題。


合理的尾部調用
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)技巧來優化:


function bounce(ret) { 
  while (typeof ret === function) { 
      retret = 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)已經完成並從堆棧中退出瞭,因此不會造成溢出。


幸運地是,ECMAScript Harmony已經考慮到瞭這一點,會自動進行優化。這對程序開發者和編譯器開發者都是有益的。


Lambdas
lambda並不神奇。簡言之,lambda就是可調用的東西,比如function,但需要遵守TCP(Tennent一致性原則,Tennent’s Correspondence Principle)。TCP要求:用一個緊鄰的lambda對表達式或代碼塊進行封裝,不會改變被封裝的代碼的含義。


很顯然,JavaScript 的 function 不是 lambda:


function one() { 
  return 1; 

 
one(); // 1
封裝後,返回值發生瞭變化:


function one() { 
  (function() { 
      return 1; 
  }()); 

 
one(); // undefined
對於接受兩個參數並將其求和的代碼塊,lambda 語法提議寫成:{|a, b| a + b}


對於上面的例子,采用 lambda 封裝將保證返回值和封裝前一樣:


function one() { 
  ({|| 
      return 1; 
  }()); 

 
one(); // 1
lambda 塊的稻草人提案目前還沒有提升到 Harmony 規范中,讓我們一起努力吧。


瀏覽器缺少什麼?
JavaScript的興衰存亡離不開瀏覽器。JavaScript的新生,也需要瀏覽器的靠譜支持。


Mozilla發起瞭一個SourceMap項目,這可以使得在調試編譯後的代碼時,能映射回源碼的對應代碼行。這太cool瞭,能極大的減少調試成本。


聽說Webkit的小夥子們也在幹同樣的事情,可惜我找不到任何證據瞭。


通曉數種語言
JavaScript在瀏覽器上的壟斷,意味著前端程序員都會同一門語言。然而,編譯器的差異性,會使得CoffeeScript程序員,很難立刻看懂基於Traceur的JavaScript代碼。


這種分歧不可避免。比如有C,同時有C++和Objective-C等各種語言。Java也一樣,基於JVM還可以選擇Clojure或JRuby。微軟意識到這一點,開發CLR.C#,Basic,IronPython等都可以運行在CLR上。


前端中的溝通障礙並非新鮮事物。一個Dojo程序員,難以立刻明白基於jQuery或YUI的代碼。


擁有多種源碼書寫語言會增加社區的溝通障礙。程序員仍需要瞭解JavaScript.至少一段時間內程序員還需要懂得JavaScript。但在短短幾年後,他們可能會更瞭解其他源碼語言。


總結
能有機會目睹JavaScript的新生,是件很棒的事情。在JavaScript編譯的競爭中,很難說誰會最終贏得市場份額,但毫無疑問,這肯定會很有趣。如今,CoffeeScript蓄勢待發,但我相信許多其他成功的源碼語言將接踵而至。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *