负暄琐话

我的email: rot47('649@ 6(hF+`hd"w=92vhG{>}G3"@l M >:>6?4@56 \F')

囧囧ID:g9yuayon
858927次访问,排名32好友20人,关注者24
姓名:g9yuayon
前世:夜郎国厚脸皮神棍
魅力指数:0
名气:1
宠物:一只从来不对生人叫的看门狗
g9yuayon的文章
原创 244 篇
翻译 4 篇
转载 48 篇
评论 849 篇
g9的公告
最近评论
icoding:恩。这年头牛人也不好混啊
icoding:离开IBM也不和咱说一下
LINSOSO:“每头成员”里面的这个“头”字用得是那么地经典。。。
xingranliuyun:老大总是能用简单的话语点起大家无限的激情。

看了这篇文章我有一种燃的感觉。

愿爱伴老大一路前行!
xingranliuyun:老大加油,相信凭你的实力没啥问题。
文章分类
收藏
    相册
    旅游
    计算机科学
    Lambda the Ultimate
    软件开发
    Reddit编程专栏(RSS)
    正在读的书
    存档
    订阅我的博客
    XML聚合  FeedSky

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

    新一篇: 微软和联想都很幽默 | 旧一篇: T61P使用体验

    更新:忘记加入对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表达式。。。。这些足够再写一篇灌水帖子了。留待下次吧。
     

    发表于 @ 2007年12月13日 13:48:00|评论(loading...)|编辑

    评论

    #Googol 发表于2007-12-13 01:25:48  IP: 58.247.121.*
    内容太艰深了,先抢沙发再细看……

    最近也在学javascript,这语言还真是很好玩。有没有编译器能把这个直接编成native的?除了各种browser,有没有命令交互式的脚本解释器?
    2007-12-13 08:11:11作者回复
    有阿。Google上可以搜出来一堆。不过我就用过ES4的体验版。:-)
    #Googol 发表于2007-12-13 01:38:15  IP: 10.194.65.*
    看完了,吃惊……

    居然动态语言静态化了,而且还有模板……不知到这个的模板实现是怎么做的,像java那样的类型消除么?

    另外,typedef里,如果typedef NewInt int,那NewInt是和int完全不同的类型(不能相互隐式转换),还是像C一样干脆就是个别名呢?

    三种命名空间……前两天还有人问我命名空间是干嘛的,这一下就仨,不摆明要把人搞晕么……
    2007-12-13 09:14:22作者回复
    我暂时还不太清楚。估计不是类型消除,不然的话白皮书应该提到很多类型消除里采用的规则。 NewInt和Int可以互相转换。
    #pongba 发表于2007-12-13 03:29:02  IP: 222.94.3.*
    老大,RSS输出全文吧:-)
    2007-12-13 08:06:55作者回复
    汗。。。都不知道这个要自己配置。已经改过来了。谢谢提醒。Orz
    #pongba 发表于2007-12-13 03:29:46  IP: 222.94.3.*
    有趣有趣,这个系列太有趣了,又要等连载了:-)
    #chenxiaoshun 发表于2007-12-13 04:57:40  IP: 210.77.27.*
    这个General function看上去不就是函数重载么?
    2007-12-13 09:16:48作者回复
    不是。重载是编译时发生的事。而generic function调用和虚函数调用一样,在运行时决定分派哪个函数。
    #interhanchi 发表于2007-12-13 07:35:06  IP: 222.76.228.*
    我的妈呀,硬要把一个匕首变成一个大刀,何必呢...
    #changqi517 发表于2007-12-13 20:58:04  IP: 202.108.130.*
    支持OO是完全正确的.其实现在js模拟面向对象的继承机制并不完善.其他的就不好说了...
    #zhucekunnan 发表于2007-12-13 21:11:45  IP: 221.122.60.*
    语言特性发展得越来越复杂化,各式各样的概念,各式各样的实现
    今后,会不会出现这样的东西:
    它抽象出所有语言的所有特性,然后以一种语言形式描述出来。我们只要学习了特性中我们关心的子集,然后学习该种语言的表达形式,就可以表达(翻译)成所有语言可理解的语句。代码的国际语,哦,我的天,让我们从所有这些复杂的东西中摆脱出来吧!只学一种语言,一些特性,就可以到处应用,抽象中的抽象。如果开发效率的重要性大于运行效率,那么这种东西总会在某个点上出现的。
    实在是厌倦了总是学习同样的特性,不同的表达了!
    #hax 发表于2007-12-14 05:18:50  IP: 207.179.28.*
    靠,几个月不见,居然比我在es4-discuss中抨击nullable的设计的时候又变复杂了一大圈。感情要发展成又一个C++。。。不过是集目前各种动态语言之大成的C++。。。
    #hax 发表于2007-12-14 05:25:13  IP: 207.179.28.*
    其实es4的某些特性可以用es3来模拟,除了class之外,像强类型系统、general function、package/unit等,我都有模拟过。
    #chaircat 发表于2007-12-15 10:27:12  IP: 121.33.122.*
    太...太...太BT了吧...
    再这么"进化"下去我转去搞逆向工程好了...
    #willko 发表于2007-12-15 21:48:06  IP: 116.24.183.*
    汗。。。
    不过离普遍还有几年时间。。。
    #skyairmj 发表于2007-12-16 07:06:16  IP: 202.114.214.*
    不是。重载是编译时发生的事。而generic function调用和虚函数调用一样,在运行时决定分派哪个函数。

    JS不就是解释运行么?应该没有编译和运行的独立阶段吧
    2007-12-18 14:54:47作者回复
    语言到这步,很难是传统地一遍又一遍在AST上走来走去地“解释”了。多少都需要先把源代码翻译成某个中间或者目标代码。重载就是在这个翻译过程中完成的。
    #yanwl 发表于2007-12-16 19:33:08  IP: 218.18.239.*
    我也觉得ES4实在太那个啥了……
    我不希望ECMAScript走这条路,个人认为它应该强化functional和范型,而不是变成现在这种样子……
    #wuwenye 发表于2007-12-16 20:24:34  IP: 222.188.162.*
    莫非都忘记为什么要OO了?
    让OO大牛都去跟多态较劲吧。我反觉得JMP、CALL清晰易懂且直指目标。
    #turingbook 发表于2007-12-17 02:39:55  IP: 222.131.250.*
    这么发展下去,很快就需要出版Accelerated JS,Effective JS,JS Common Knowledge之类的书了。
    #turingbook 发表于2007-12-17 02:43:43  IP: 222.131.250.*
    前几天还想写篇东西说说JS的演变呢,袁老大此文一出,可以省省力气了。呵呵。
    #ssssssssssss 发表于2008-04-01 03:54:49  IP: 211.103.240.*
    JS 为什么要升级到JS2?
    我觉得楼主没写道主要的矛盾。
    JS2和新的Taramin engine比JS可以带来执行效率上的飞跃,那么为什么要这个飞跃呢?
    1。 RIA。 Flash/AIR 出来了,Silverlight也出来了,但AS3.0和C#的效率都显然比JS高很多,做RIA再光依赖于JS1.X就很一厢情愿了。
    2。Mozilla自己的平台。Mozilla平台本身就是用JS做为胶水语言的,JS2性能上的提升对于Mozilla平台的性能提升是显而易见的。
    3。像DOM/SVG/CANVAS(3D?)的操作,不用JS2,性能上根本不能让人满意!
    ------------------
    JS2 革了 JS1的命么?
    没有。
    JS1还是会大量用于WEB,JS2主要用于Mozilla相关开发和RIA,并不冲突,这种进步不可忽视!
    ------------------
    JS2的未来?
    希望和AS3能合并吧,Flash全部开源捐给Mozilla,这样可能才有对抗silverlight的胜算。。。
    #ssssssssssss 发表于2008-04-01 04:42:51  IP: 211.103.240.*
    等待下篇大作 :)
    #dead_of_winter 发表于2008-04-01 08:45:59  IP: 218.7.43.*
    ES4/JS2根本就是纯粹的垃圾 将ES3的优美完全破坏 靠着一些恶心的语言特性实现一些根本不必要的功能。
    人月神话中说 第二个项目是一个架构师最危险的项目 Brendan Eich在这js第二版上恐怕也要犯这样的错误吧

    一直比较迷恋现在的JS,这门语言几乎是一门完美的弱类型动态语言,没有C++那种编译时小花招,支持了meta-class,prototype,closure,我甚至用它实现了currying,但看到现在的es4,只感觉到深深的绝望。

    作为一门弱类型语言,在语言级别提供类型检查的确是必要的,但ES4的这种机制,则是强类型和弱类型的混合,集合了二者的缺点,可以说是前所未见的糟糕设计。

    更糟糕的是,抄袭其他语言的类定义和命名空间管理,把原来的一个语法域拆成各种声明域和执行域,一方面给语言的实现和优化制造了麻烦,另一方面也让语言的设计思路的美感尽失。如LISP那样的简洁构造才是计算机语言应该追求的。

    最后说一句 向下兼容绝不是设计丑陋的ES4的托辞,相反如果ES4不去兼容ES3,还能勉强算作一门没什么创意的新语言,而现在的ES4,纯粹是背负着ES3动态性和弱类型包袱去抄袭C#、Java的一个失败品。
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © g9