JavaScript这浓眉大眼的也背叛革命了(一)

82 篇文章 4 订阅
72 篇文章 3 订阅
更新:忘记加入对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表达式。。。。这些足够再写一篇灌水帖子了。留待下次吧。
 
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 24
    评论
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值