JavaScript這濃眉大眼的也背叛革命了(一)

來源:互聯網
上載者:User
更新:忘記加入對generic function的概述了。剛才補上。另外chenxiaoshun老大提了個很好的問題:generic function和function overloading有什麼區別?區別就是,調用哪個generic function是在運行時決定的,同調用虛函數實現多態一致。而重載函數是在編譯時間確定的。補充的內容是:Generic function就是用來解決這類多指派問題的。運行時調用generic function時,會根據該函數的*所有*參數決定指派對象。總的規則是越具體的類型佔用越高的優先順序。比如說foo(Number)比foo(Object)有更高的優先順序,因為Number是Object的子類,比Object具體。另外,generic 函數裡所有參數的指派權重一樣,所謂的對稱多指派。Groovy採用了不對稱多指派。系統會先比較第一個參數。如果不能決定,再比較第二個。。。兩個半月前的舊聞。不過今天才稍有閑暇,抽空八卦。JavaScript 2, 也即ECMAScript 4(簡稱ES4)的官方綜述出籠,Yahoo!的Douglas Crockford就 怒了,因為ES4實在有悖他希望JavaScript繼續短小精悍的理念。用他的話說,ES4添加的東西比大家用得正歡的ES3本身還龐大,是可忍孰不可忍。改名,改名!JavaScript家族沒有這個怪胎。IE的技術頭目Chris Wilson也怒了,認為ES4加入太多特性,已經改變了JavaScript的特質。Mozilla的CTO,JavaScript的主創,Brendan Eich自然不會示弱,撕下溫良木訥的geek面紗,用公開信高調指責微軟,然後回複geek本色,以一場精彩講座回應技術質疑(用FireFox+NoScript外掛程式的老大們記著把暫時允許該網站,不然看不到投影片的效果)。順便跑題一下。堅決反對“極客”這個翻譯。Geek是具有某類特質的人,和職業或身份扯不上關係。客個CC啊。這個翻譯和偽小資們把home party翻譯成“轟趴”一樣不著四六。不,冷靜下來想一下,“極客”好否只是個人喜好。“轟趴”才真地齷齪。土沒有關係,但明明是土老財還人前人後硬說自己是買辦,就不對了。當然,一想到“代碼大全”這個釘在恥辱柱上的翻譯,俺的心態又平和了。JavaScript社區立馬分成捧ES4的,鬥ES4的,和騎牆的,好比正龍拍虎掀起的群眾運動。大尾巴狼Robert Scoble一貫人來瘋,拋出一篇不靠譜文章,學布希在大選前拉橙色警報,告誡人們小心JavaScript的分野導致新的瀏覽器戰爭,結果暴露了不做功課的面目:ES4基本是ES3的超集。向後相容是ES4的目標之一。少數不相容的改動也不是搶雞蛋的大事,比如prototype無需修改就能在ES4下運行。大不了微軟繼續用ES3。戰爭在哪裡?再說,JavaScript早已分裂,JavaScript, Jscript, ActionScript, OpenLaszlo JavaScript。。。五馬分屍的分呐。 政治非俺所好,還是談談ES4本身。一句字:全,代碼大全的全。ES4彷彿囊括了近30年來動態語言的流行特性。白皮書裡有很多執行個體代碼,這裡就不抄書了。只單調地列出部分有趣的特性。 
  • OOP。ES4新加了interface和class。慣用prototype+closure類比各式OOP的老大多半惡向膽邊生了。熟讀SICP一類地下刊物的老大們也多半輕蔑滴拋出了殺手級名言:Object is just poor man’s closure。其實我挺歡迎這些特性。幾乎每套 流行 架構都要用類比OOP。不用流行類庫的老大們寫的第一段代碼也多半如下:
// makeClass - By John Resig (MIT Licensed)
function makeClass (){
  return function(args ){
    if ( this instanceof arguments.callee ) {
      if ( typeof this.init == "function" )
        this.init.apply ( this, args.callee ? args : arguments );
    } else
      return new arguments.callee ( arguments );
  };
}既然這樣,幹嘛不從語言層級支援該項高度重複的工作呢?既可以增進代碼間的interoperability,還能提供虛擬機器層級的最佳化。明明一個class關鍵詞可以解決的問題,為什麼要用一層,兩層,甚至三層閉包來類比呢?不錯,Object is poor man’s closure。但我們也可以說Closure is poor man’s object。更重要的是,我們 需要固定或者保護某些屬性。這用ES3現有的功能根本不能實現(比如for..in迴圈總是遍曆對象類所有屬性。我們必須人肉過濾)。ES4支援常見的OOP功能:
    • 繼承用extends關鍵詞。實現interface用implements關鍵詞。常見的關鍵詞static, final, override 通通支援。用慣Java和C#的老大可以笑了。

    • 用關鍵詞dynamic修飾的類裡可以在runtime添加活刪改屬性。支援getter和setter,所以Ruby和C#的老大們可以興奮了。通過關鍵字meta支援類似Ruby的method_missing()方法。這點相當不錯。好多Ruby裡流行的慣用法,尤其是用來實現DSL的慣用法可以派上用場了。
    • 類的屬性支援三種不同的修飾:DontDelete :該屬性固定,不能被動態移除; DontEnum:該屬性不會在for(var p in object)的迴圈中出現; 和ReadOnly:該屬性的值是常數。一旦確定,不能更該。普通class裡的屬性是DontDelete和DontEnum。用dynamic class申明的動態類的屬性既非DontDelete,也非DontEnum。
    • Generic function。也就是人們常說的multimethod,或者multiple dispatch method。這對用慣了C++操作符重載,CLOS和Dylan裡generic method的老大們來說,正是重大利好訊息啊。Generic function不是新鮮概念。1986年的OOPSLA會議上,一幫LISP Hacker提交了著名的論文CommonLoops – Merging Lisp and Object-Oriented Programming,裡面正式提到了multimethod這個術語。據CLOS的作者Gregor Kiczales(擁護AOP的老大們應該覺得很親切吧?)回憶,他的合作者Larry Masinter最早合成了multimethod這個詞。流行的OO語言,比如Java/C#/C++/Python/Ruby什麼的,通常只支援單指派方法。也就是說方法指派只與方法的調用者或者某一個參數(比如python裡第一個參數self)有關。當我們看到caller.foo()時,我們就知道運行時調用的是對象caller裡的方法foo()。可惜現實世界裡的關係沒有那麼簡單。Wikipedia用物體碰撞作例子。當我們想實現兩類物體的碰撞時,到底在哪裡實現我們的方法呢?比如說,我們想讓飛船同隕石碰撞,到底把collide()方法放到spaceship裡,還是放到asteroid裡呢?如果要限定碰撞的類別呢?比如說友方飛船不能碰撞,但可以同敵方飛船碰撞呢?如果我們希望多個物體碰撞呢?如果我們的代碼寫好後,允許代碼的使用者添加新的物體和碰撞過程,該怎麼辦呢?Generic function就是用來解決這類多指派問題的。運行時調用generic function時,會根據該函數的*所有*參數決定指派對象。總的規則是越具體的類型佔用越高的優先順序。比如說foo(Number)比foo(Object)有更高的優先順序,因為Number是Object的子類,比Object具體。另外,generic 函數裡所有參數的指派權重一樣,所謂的對稱多指派。Groovy採用了不對稱多指派。系統會先比較第一個參數。如果不能決定,再比較第二個。。。

      再看個常用的Visitor模式。我們用普通的運算式處理作例子。
      interface VisitableExpression{
          function accept(visitor: ExpressionVisitor); //是滴,ES4開始支援靜態類型申明了
      }

      class AddExpression implements VisitableExpression{
          var lefOperand;
          var rightOperand;

          function accept(visitor:ExpressionVisitor){
              visitor.visitAddExpression(this);
          }
      }

      class IntExpression implements VisitableExpression{
          var value;

          function accept(visitor:ExpressionVisitor){
              visitor.visitIntExpression(this);
          }
      }

      interface ExpressionVisitor{
          function visitAddExpression(expression:VisitableExpression);
          function visitIntExpression(expression: :VisitableExpression);
      }

 這段代碼問題不少。添加新的Visitor容易了。比如說求值Visitor,列印Visitor,或者後門注入Visitor。但添加新的ComplexExpression呢?這下每一個現有的Visitor都要改動。如果我想改動ExpressionVisitor裡方法的簽名呢—本來是件很美好的事,非被VisitableExpression的僵硬結構搞成醜聞。有了generic function,Visitor模式基本消失:
generic function evaluate(e); //generic function必須先申明 generic function evaluate(e: AddExpression){...}
generic function evaluate(e: IntExpression){...}

這裡的evaluate代替了臃腫的class EvaluateVisitor implements ExpressionVisitor。ES4會根據運行時evaluate參數的類型決定調用哪一個evaluate()函數。如果添加了新的運算式,比如說ComplexExpression,我們只需相應添加函數就行了。generic function evaluate(e: ComplexExpression){...} 同理,如果我們要處理碰撞問題:
generic function collide(a, b); generic function collide(s: Spaceship, a: Asteroid){...}
generic function collide(f: FriendSpaceship, e: EnemySpaceship){...}
generic function collide(as: [asteroid], p: Planet){...} //一堆隕石同行星碰撞 如果要加入新的物體和碰撞方法再簡單不過。添加一個新的generic function就成了。完全不用改動任何已有的實現。

下面是課堂測驗時間。用generic function改寫下面的Builder模式(圖取自GoF):

關鍵是這個ConvertXXX()方法。用generic function改寫該方法後,Builder/Director就不再需要了。ConvertXXX()的邏輯被指派到完全正交的convert()函數中。以後添加新的轉換方法也變得簡單。 generic function convert(token, tokenType, target); generic function convert(token:Token, type:Char, target:Tex){…}generic function convert(token:Token, type:Font, target:Tex){…}
generic function convert(token:Token, type:Char, target:ASCII){…}….. 利用generic function,我們也就能實現操作符重載。比如重載複數的加法:generic intrinsic function +( a: Complex, b: Complex )
     new Complex( a.real + b.real, a.imag + b.imag )

generic intrinsic function +( a: Complex, b: AnyNumber )
    a + Complex(b)

generic intrinsic function +( a: AnyNumber, b: Complex )
    Complex(a) + b 這裡的intrinsic是ES4內建的namespace。是滴,ES4引入了namespace,還不只一種。相當邪惡。

  • 類型系統。當然OOP裡提到的class和interface也是類型系統的一部分(所謂的Nominal Type),但它們用的太廣泛,就單獨提出來聊了。

    • 加入了Record和Array類型。{a:int, b:char}是個Record。[int]是整數數組。是滴,ES4引入了int和char,不再像以前一樣,浮點數和string包辦一切了。不光如此,ES4也引入了Wrapper。比如boolean的wrapper是Boolean,double的wrapper是Double。這點俺其實不理解。當初Java區分primitive和wrapper類型是處於效能的考慮。ES4有必要這麼做嗎?

    • Annotation。也就是前面看到的類型申明。比如var a:int;表示申明了類型為int的變數a。類型申明是可選的。函數參數,變數申明,函數傳回值,常數申明時,都可以加上類型標註。這和普通的靜態語言沒有什麼區別。這保證了ES3裡沒有類型申明的代碼同ES4相容。可選類型支援所謂的漸進式開發。我們可以從完全動態程式開始,隨著業務模型的穩定逐漸加入類型申明。編譯期的類型系統也有助於IDE開發,編譯器驗證代碼,文檔產生。。。好處還是很多的。
    • 函數類型。function (this:C, int): boolean代表了C類裡接受int參數,返回boolean的函數。這個有什麼用呢?用ES3的時候,我們常常通過傳遞匿名函數來實現輕量級取代Strategy模式。問題是,所有匿名函數都是一個類型:Function。有了函數類型,我們就可以限制可以接受的函數了。這點想必也受Haskell/ML老大們的青睞。不過申明類型時寫那麼一長串也是自虐,所以類型定義粉墨登場:
  • Type definition。類型定義類似C/C++裡的typedef。我們用關鍵詞type定義新的類型:Type IntComparator function(a: int, b:int): int。這段代碼定義了一個新的函數類型IntComparator。如果我們寫了一個給整數數組排序的函數sortInt(intArray: [int], comparator:IntComparator),就不用擔心接受一個比較字串的函數了。ES4的typedef比C/C++的要強大。任何一個type申明都同時定義了該類型的中繼資料。中繼資料實現了meta-object interface,據在運行時通過反射讀取。同時,定義的類型可以嵌套,但不允許遞迴(包括互動遞迴)。比如說我們定義類型Person:type Person = { name:{ last:string, first:string }, born:Date, spouse:* }。如果spouse的類型是Person,現在ES4環境運行時會陷入死迴圈,吃掉大量記憶體。將來多半編譯器會報錯。的run.exe就是ES4的執行環境。

    有了類型,怎麼能沒有子類型(subtype)呢?S <: T表示類型S是類型T的子類型。子類型有一堆判斷規則。有興趣地可以到白皮書第16頁觀賞。另外,有了類型,自然得考慮類型間的轉換。於是兩個強大的關鍵詞,like(慣使Eiffel的老大可以笑了)和wrap粉墨登場。關鍵字like用於弱化的類型判別,頗有模式比對的風格。可以申明var v: like { x: int, y: int }。那任何形如{int, int}類型的值都可以賦給變數v。比如說var p:Point, 那v = p是合法的指派陳述式。like也可以做為操作符試用,比如說{a:10, b:20} is like Point會返回true。這好比資料的duck typing。我們不管值的類型是什麼,只要關心它的“形狀”。這樣的話,我們既可以設定資料的結構,又不用被資料的類型限制得太死。關鍵詞wrap同like類似,但把變數的類型強行“封裝”被比較的類型。比如下面的代碼裡,變數v的類型就轉換為{x:int, y:int}的類型:

                var v: wrap { x: int, y: int } = { x: 10, y: 20 }
               var w: { x: int, y: int } = v

    當作操作符用時,給定兩變數v和t,如果v is t返回true,則 v wrap t返回變數v,不然如果v is like t為真,則返回一個新對象o, 使得v is o為真。再不然就拋出TypeError的異常羅。可以想象這兩個操作符會衍生出許多靈活的應用。有興趣的可以到這裡看更多的例子。

  • Parametric Types。也就是我們說的類型模板,或者泛型。比如下面的代碼建立了一個容納任何類型的Pair:class Pair.<T> {
        var first: T, second: T
    }

    函數,類,介面,和類型都支援參數類型。類裡又能申明函數。衛生問題就出現了。比如下面的代碼裡,變數x的類型到底是什嗎?


    class Pair.<T> {
        var first: T, second: T;
        
        function f.<T>(){
            var x: T;
        }
    }
    所以這時用類型定義就派上用場了:


    class Pair.<T> {
        var first: T, second: T;
        
        Type XT = T;
        function f.<T>(){
            var x: XT;
        }
    }

下面插播一首詩:衛生問題在支援宏的語言裡非常重要。《Beautiful Code》裡專門有一章討論它。強烈推薦。

看官還沒有看煩的話,小的都寫煩了。實在是因為ES4的改動太多。ES4引入了命名空間的管理。而且不引則已,一引就仨:package, namespace,和program unit。另外ES4加入了list comprehension, Python風格的iterator和generator。Nullable type。尾遞迴最佳化,pragma,expression closure, destructive assignment, Java 風格的try catch, 改良的this, 改良的arguments參數,改良的with,程式啟動並執行鉤子(meta-level hooks), 俺私下盼望已久的&&=和||=操作符,建立local binding的let運算式。。。。這些足夠再寫一篇灌水文章了。留待下次吧。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.