ajax相关--查看文章
 
《悟透JavaScript》之 甘露模型(新)
发布时间:2008-8-2
  • 上一篇文章:
  • 下一篇文章:

  • 在上面的示例中,我们定义了两个语法甘露,一个是Class()函数,一个是New()函数。使用Class()甘露,我们已经可以用非常优雅的格式定义一个类。例如前例中的:

        var Employee = Class(Person,    //派生至Person类
        {
            Create: 
    function(name, age, salary)
            
    {
                Person.Create.call(
    this, name, age);  //调用基类的构造函数
                this.salary = salary;
            }
    ,
            ShowMeTheMoney: 
    function()
            
    {
                alert(
    this.name + " $" + this.salary);
            }

        }
    );

        这种类的写法已经和C#或Java的格式非常相似了。不过,其中调用基类的构造函数还需要用“Person.Create.call(this, name, age)”这样的方式来表达。这需要用到基类的类名,并要用call这种特殊的方式来传递this指针。这和C#的base()以及Java的super()那样的简介调用方式比起来,还需要进一步美化。

        而New()函数的使用也不是很爽。前例中需要用“New(Employee, ["Steve Jobs", 53, 1234])”这样的方式来创建对象,其中第一个参数是类,其他构造参数需要用数组包起来。这和JavaScript本来那种自然的“new Employee("Steve Jobs", 53, 1234)”比起来,丑陋多了。这也需要美化。

        为了实现这些美化工作,我们需要回顾一下new一个对象的实质。前面我们说过:
        var anObj = new aClass();
        相当于先创建一个空白对象anObj,然后将其作为this指针调用aClass()函数。其实,这个过程中还有一个关键步骤就是将aClass的prototype属性,赋值给anObj内置的prototype属性。尽管我们无法访问到anObj内置的prototype属性,但它却为对象提供了可以调用的方法。

        由于前例中的Class()语法甘露实际上是构造了一个原型,并将这个原型挂在了相应的原型链上。由于它返回的是一个对象而不是函数,因此由它定义出来的Person和Employee类也都只是对象而不是函数,无法用new Person()或new Employee()这样的方式来创建对象。要创基于一个原型来创建对象,就需要借助New()语法甘露来中转这个原型。

        那么,如果我们让Class()语法甘露返回一个函数而不是对象,不就可以用new Person()和new Employee()这种方式来创建对象了吗?而且,我们可为这个返回函数创建一个继承至相关原型链的原型对象,并设置到该函数的prototype属性。这样,我们用new方式创建这个类函数的对象时,就自然地继承了该类的原型了。

        那么,我们让Class()语法甘露返回什么函数呢?因为Class()语法甘露返回的函数是用来创建对象的,当然应该返回该类的构造函数了,正好可以是类定义参数中的Create方法啊。这样一来,我们也无需在New()语法甘露中间接调用Create构造函数了,事实上New()语法甘露可以完全扔掉了。

        于是,我们就有了下面这个精简甘露模型的例子:

    http://www.leadzen.cn/Books/WuTouJavaScript/1/JS24.htm

    <script type="text/javascript">
        
    //定义类的语法甘露:Class()
        //最后一个参数是JSON表示的类定义
        //如果参数数量大于1个,则第一个参数是基类
        //第一个和最后一个之间参数,将来可表示类实现的接口
        //返回值是类,类是一个构造函数
        function Class()
        
    {
            
    var aDefine = arguments[arguments.length-1]; //最后一个参数是类定义
            if(!aDefine) return;
            
    var aBase = arguments.length>1 ? arguments[0] : object; //解析基类
            
            
    function prototype_(){}//构造prototype的临时函数,用于挂接原型链
            prototype_.prototype = aBase.prototype;  //准备传递prototype
            var aPrototype = new prototype_();    //建立类要用的prototype
            
            
    for(var member in aDefine)  //复制类定义到当前类的prototype
                if(member!="Create")    //构造函数不用复制
                    aPrototype[member] = aDefine[member];

            
    if(aDefine.Create)  //若有构造函数
                var aType = aDefine.Create  //类型即为该构造函数
            else    //否则为默认构造函数
                aType = function()
                
    {
                    
    this.base.apply(this, arguments);
                }
    ;

            aType.prototype 
    = aPrototype;   //设置类(构造函数)的prototype
            aType.Base = aBase;             //设置类型关系
            aType.prototype.Type = aType;   //为本类对象扩展一个Type属性
            return aType;   //返回构造函数作为类
        }
    ;

        
    //根类object定义:
        function object(){}    //定义小写的object根类,用于实现最基础的方法等
        object.prototype.isA = function(aType)   //判断对象是否属于某类型
        {
            
    var self = this.Type;
            
    while(self)
            
    {
                
    if(self == aType) return true;
                self 
    = self.Base;
            }
    ;
            
    return false;
        }
    ;
        
        object.prototype.base 
    = function()  //调用基类构造函数
        {
            
    var Caller = object.prototype.base.caller;
            Caller 
    && Caller.Base && Caller.Base.apply(this, arguments);
        }
    ;
        
        
    //语法甘露的应用效果:    
        var Person = Class      //默认派生自object基本类
        ({
            Create: 
    function(name, age)
            
    {
                
    this.base();
                
    this.name = name;
                
    this.age = age;
            }
    ,
            SayHello: 
    function()
            
    {
                alert(
    "Hello, I'm " + this.name + "" + this.age + " years old.");
            }

        }
    );
        
        
    var Employee = Class(Person,    //派生自Person类
        {
            Create: 
    function(name, age, salary)
            
    {
                
    this.base(name, age);  //调用基类的构造函数
                this.salary = salary;
            }
    ,
            ShowMeTheMoney: 
    function()
            
    {
                alert(
    this.name + " $" + this.salary);
            }

        }
    );

        
    var BillGates = new Person("Bill Gates"53);
        
    var SteveJobs = new Employee("Steve Jobs"531234);
        BillGates.SayHello();
        SteveJobs.SayHello();
        SteveJobs.ShowMeTheMoney();
        
        
    var LittleBill = new BillGates.Type("Little Bill"6); //用BillGate的类型建LittleBill
        LittleBill.SayHello();
        
        alert(BillGates.isA(Person));       
    //true
        alert(BillGates.isA(Employee));     //false
        alert(SteveJobs.isA(Person));       //true
    </script>

        这个精简甘露模型模拟出来的类更加自然和谐,而且比前面的甘露模型更加精简。其中的Class()函数虽然很简短,但却是整个模型的关键:

        Class()函数将最后一个参数当作类定义,如果有两个参数,则第一个参数就表示继承的基类。如果多于两个参数,则第一个和最后一个之间的参数都可以用作类需要实现的接口声明,保留给将来扩展甘露模型使用吧。

        使用Class()函数来定义一个类,实际上就是为创建对象准备了一个构造函数,而该构造函数的prototype已经初始化为方法表,并可继承上层类的方法表。这样,当用new操作符创建一个该类对象时,也就很自然地将此构造函数的原型链传递给了新构造的对象。于是,就可以采用象“new Person("Bill Gates", 53)”这样的语法来创建对象了。

        类定义中名为Create的函数是特别对待的,因为这就是构造函数。如果没有定义Create构造函数,Class()函数也会创建一个默认构造函数。事实上,这个构造函数就代表了类。除此之外,我们还为其定义了一个Base属性,以方便追溯继承关系。

        在本例中的根类object的原型中,我们定义了一个base方法。有了这个方法之后,在类定义的构造函数中,就可以使用“this.base()”这样的方式来调用基类的构造函数。这种调用基类的方式和C#的base及Java的super就非常相似了。

        不过,Class()函数中还是有个小问题,那就是不支持toString()方法的覆写。也就是说,如果我们为一个类定义了自己的toString()方法,调用Class()函数来生成类时,toString()方法会丢失。

    原来toString()方法被JavaScript规定为不可枚举的内置方法。除此之外还有还有toLocaleString(), valueOf(), hasOwnProperty(), isPrototypeOf(), propertyIsEnumerable()等,都是不能枚举的内置方法。这样在Class()函数中的那个for(…in…)语句就不能遍历到这些属性,导致问题的产生。

    因此,这个Class()语法甘露还不能支持不可枚举内置方法的覆写。这不能不说是一个小小的遗憾。当然,遇到需要完全覆写这些内置方法的情况并不多。顶多偶尔会有toString(), toLocaleString(), valueOf()这三个方法的覆写,其他几个几乎不会有覆写的情况。

    除此之外,object根类的base()方法也还有个小问题。这个方法用到了函数的caller属性,以此判断构造函数的层次。而Opera浏览器不支持函数的caller属性,因此base方法不适合于Opera浏览器,这不能不说是另一个更大的遗憾。

        如果在甘露模型中留有这样的问题,想必观音姐姐也会遗憾。我们还需要继续努力,别让观音姐姐失望啊。

        其实,要解决不能覆写非枚举属性的问题也并非难事。既然这些属性是特殊的,我们就可以对其进行特殊处理。我们可以在复制完可枚举的属性之后,加上类似下面的特殊判断处理语句:

            if(aDefine.toString != Object.prototype.toString)
                aPrototype.toString = aDefine.toString;

        因为,如果覆写了toString方法,那么它就肯定不等于原生的toString方法,这时就可复制toString方法。当然,我们也可以不通过比较,而是用hasOwnProperty来判断是否覆写了toString方法:

            if(aDefine.hasOwnProperty("toString"))
                aPrototype.toString = aDefine.toString;

        使用哪种判断方式可以任选。我们建议采用直接比较方式,这样可以避免万一遇到覆写Object.prototype.hasOwnProperty的情况,也不至于出问题。当然,这也似乎有点太钻牛角尖了。
        在实际的应用中,我们建议根据具体应用情况来决定是否需要支持特殊属性的覆写。如果在应用中根本不会覆写这些特殊属性,就无需加上这样的特殊处理。如果是打造专业的AJAX类库,最多支持toString(), toLocaleString(), valueOf()这三个方法的覆写就可以了。千万不要玩画蛇添足的游戏。

        最头痛的是对base()函数的重写,也就要兼容不支持caller属性的Opera浏览器。虽然Opera浏览器目前只占很小的市场范围,但也算有名的四大浏览器之列。如果甘露模型不支持Opera浏览器,显然无法彰显观音菩萨的法力,也更不好意思说自己是观音老师的弟子。

        其实,base()方法之所以要使用自身的caller属性,就是为了确定当前构造函数的层次,从而可以知道该调用更上层的构造函数。有没有别的办法来知道是那层构造函数调用了base()方法呢?残酷的事实告诉我们,除了函数自身的caller属性,没有办法知道是谁调用了自己。

        既然改变不了别人,那就改变自己!我们为什么就不能在运行中改变base()自身呢?

        事实上,第一层构造函数调用this.base()时,我们是可以过this.Type属性知道地一层构造函数的,而this.Type.Base就是第二层构造函数。只是,第二层构造函数又会调用this.base(),其本来是想调用第三层的构造函数,但再次进入base()函数时,就无法知晓构造函数的层次了。

    如果我们在第一层构造函数调用进入this.base()时,先改变this.base本身,让其在下次被调用时能掉到第三层构造函数。完成这个变身动作之后再调第二层构造函数,而第二层构造函数再调用this.base()时就能调用到第三层构造函数了。这样,只要我们在每次的base()调用中都完成一个自我的变身动作,就可以按正确的顺序完成对构造函数的调用。这是多么有趣的调用方式啊!

        于是,我们可以将原来的base()函数改写成下面的形式:

        object.prototype.base = function()  //调用基类构造函数
        {
            
    var Base = this.Type.Base;  //获取当前对象的基类  
            if(!Base.Base)  //若基类已没有基类
                Base.apply(this, arguments)     //则直接调用基类构造函数
            else    //若基类还有基类         
            {
                
    this.base = MakeBase(Base);     //先覆写this.base
                Base.apply(this, arguments);    //再调用基类构造函数
                delete this.base;               //删除覆写的base属性
            }
    ;
            
    function MakeBase(Type) //包装基类构造函数
            {
                
    var Base = Type.Base;
                
    if(!Base.Base) return Base; //基类已无基类,就无需包装
                return function()   //包装为引用临时变量Base的闭包函数
                {
                    
    this.base = MakeBase(Base);     //先覆写this.base
                    Base.apply(this, arguments);    //再调用基类构造函数
                }
    ;
            }
    ;
        }
    ;

        原来的base()函数只有两行代码,而新的base()函数却又十几行代码。看来为了支持Opera浏览器确实也付出了代价,好在这些代码只有十来行,代价并不是太大。当然,如果无须支持Opera浏览器,也就不必用这个base()函数了。

        在这个新的base()函数中,对this.base的覆写实际上只是在this对象身上创建了一个临时的base方法。这个临时方法暂时遮住了object.prototype.base方法,而object.prototype.base方法却一直存在。而每次对this.base的变身操作,都是针对这个临时的方法的。当所有层次构造函数的调用都完成之后,即可删除this对象的这个临时base方法。

        其中的MakeBase()函数非常有意思,如果基类还有基类,它就返回一个闭包函数。下次this.base()被构造函数调用时,即调用的是这个闭包函数。但这个闭包函数又可能会调用MakeBase()形成另一个闭包函数,直到基类再无基类。

        如果说这是递归调用呢?却并非那种函数自身对自身的直接或间接调用,而是调用一个函数却返回另一个函数,再调用返回的函数又会在其中产生新的返回函数。如果说是函数式编程中的高阶函数调用呢?这函数的嵌套阶数却是与类层次相关的不确定数,而每一个阶梯都有新生成的函数。

    这可真是闭包中嵌套着闭包,貌似递归却又不是递归,是高阶函数却又高不可测。一旦整个对象创建完成,用过的内存状态都释放得干干净净,只得到一尘不染的新建对象。JavaScript玩到这样的境界,方显观音大士的法力!

        下面就是是重写后的完美甘露模型代码:

    http://www.leadzen.cn/Books/WuTouJavaScript/1/JS25.htm


    <script type="text/javascript">
        
    //定义类的语法甘露:Class()
        //最后一个参数是JSON表示的类定义
        //如果参数数量大于1个,则第一个参数是基类
        //第一个和最后一个之间参数,将来可表示类实现的接口
        //返回值是类,类是一个构造函数
        function Class()
        
    {
            
    var aDefine = arguments[arguments.length-1]; //最后一个参数是类定义
            if(!aDefine) return;
            
    var aBase = arguments.length>1 ? arguments[0] : object; //解析基类
            
            
    function prototype_(){}//构造prototype的临时函数,用于挂接原型链
            prototype_.prototype = aBase.prototype;  //准备传递prototype
            var aPrototype = new prototype_();    //建立类要用的prototype
            
            
    for(var member in aDefine)  //复制类定义到当前类的prototype
                if(member!="Create")    //构造函数不用复制
                    aPrototype[member] = aDefine[member];
                    
            
    //根据是否继承特殊属性和性能情况,可分别注释掉下列的语句
            if(aDefine.toString != Object.prototype.toString)
                aPrototype.toString 
    = aDefine.toString;
            
    if(aDefine.toLocaleString != Object.prototype.toLocaleString)
                aPrototype.toLocaleString 
    = aDefine.toLocaleString;
            
    if(aDefine.valueOf != Object.prototype.valueOf)
                aPrototype.valueOf 
    = aDefine.valueOf;

            
    if(aDefine.Create)  //若有构造函数
                var aType = aDefine.Create  //类型即为该构造函数
            else    //否则为默认构造函数
                aType = function()
                
    {
                    
    this.base.apply(this, arguments);   //调用基类构造函数
                }
    ;

            aType.prototype 
    = aPrototype;   //设置类(构造函数)的prototype
            aType.Base = aBase;             //设置类型关系,便于追溯继承关系
            aType.prototype.Type = aType;   //为本类对象扩展一个Type属性
            return aType;   //返回构造函数作为类
        }
    ;

        
    //根类object定义:
        function object(){}    //定义小写的object根类,用于实现最基础的方法等
        object.prototype.isA = function(aType)   //判断对象是否属于某类型
        {
            
    var self = this.Type;
            
    while(self)
            
    {
                
    if(self == aType) return true;
                self 
    = self.Base;
            }
    ;
            
    return false;
        }
    ;
        
        object.prototype.base 
    = function()  //调用基类构造函数
        {
            
    var Base = this.Type.Base;  //获取当前对象的基类  
            if(!Base.Base)  //若基类已没有基类
                Base.apply(this, arguments)     //则直接调用基类构造函数
            else    //若基类还有基类         
            {
                
    this.base = MakeBase(Base);     //先覆写this.base
                Base.apply(this, arguments);    //再调用基类构造函数
                delete this.base;               //删除覆写的base属性
            }
    ;
            
    function MakeBase(Type) //包装基类构造函数
            {
                
    var Base = Type.Base;
                
    if(!Base.Base) return Base; //基类已无基类,就无需包装
                return function()   //包装为引用临时变量Base的闭包函数
                {
                    
    this.base = MakeBase(Base);     //先覆写this.base
                    Base.apply(this, arguments);    //再调用基类构造函数
                }
    ;
            }
    ;
        }
    ;

        
    //语法甘露的应用效果:    
        var Person = Class      //默认派生自object基本类
        ({
            Create: 
    function(name, age)
            
    {
                
    this.base();    //调用上层构造函数
                this.name = name;
                
    this.age = age;
            }
    ,
            SayHello: 
    function()
            
    {
                alert(
    "Hello, I'm " + this.name + "" + this.age + " years old.");
            }
    ,
            toString: 
    function()    //覆写toString方法
            {
                
    return this.name;
            }

        }
    );
        
        
    var Employee = Class(Person,    //派生自Person类
        {
            Create: 
    function(name, age, salary)
            
    {
                
    this.base(name, age);  //调用基类的构造函数
                this.salary = salary;
            }
    ,
            ShowMeTheMoney: 
    function()
            
    {
                alert(
    this + " $" + this.salary); //这里直接引用this将隐式调用toString()
            }

        }
    );

        
    var BillGates = new Person("Bill Gates"53);
        
    var SteveJobs = new Employee("Steve Jobs"531234);
        alert(BillGates);   
    //这里将隐式调用覆写后的toString()方法
        BillGates.SayHello();
        SteveJobs.SayHello();
        SteveJobs.ShowMeTheMoney();
        
        
    var LittleBill = new BillGates.Type("Little Bill"6); //用BillGate的类型建LittleBill
        LittleBill.SayHello();
        
        alert(BillGates.isA(Person));       
    //true
        alert(BillGates.isA(Employee));     //false
        alert(SteveJobs.isA(Person));       //true
    </script>

        当今的JavaScript世界里,各式各样的AJAX类库不断出现。同时,在开放Web API的大潮中,AJAX类库作为Web API最重要的形式,起着举足轻重的作用。这些AJAX类库是否方便引用,是否易于扩展,是否书写优雅,都成了衡量Web API质量的重要指标。

        甘露模型基于JavaScript原型机制,用及其简单的Class()函数,构造了一个非常优雅的面向对象的类机制。事实上,我们完全可以在这个甘露模型的基础上打造相关的的AJAX类库,为开发人员提供简洁而优雅的Web API接口。

    想必微软那些设计AJAX架构的工程师看到这个甘露模型时,肯定后悔没有早点把AJAX部门从美国搬到咱中国的观音庙来,错过了观音菩萨的点化。

        当然,我们也只能是在代码的示例中,把Bill Gates当作对象玩玩,真要让他放弃上帝转而皈依我佛肯定是不容易的,机缘未到啊!如果哪天你在微软新出的AJAX类库中看到这种甘露模型,那才是真正的缘分!


    广告时间:《悟透JavaScript》一书,共分三部分,
           第一部分 JavaScript真经 (博客原文扩编)
           第二部分 手谈JavaScript (讲述JavaScript操纵DOM)
           第三部分 点化AJAX (AJAX思维升华)

    就算你不看,也可以给孩子当故事书。
    如果你还没孩子,就当给自己买本幽默的漫画书。

    原创枪手:李战(leadzen)
    幕后指使:电子工业出版社-博文视点公司
    原文:http://www.cnblogs.com/leadzen/archive/2008/06/04/1213090.html
     


    免费电影
    设为首页
    加入收藏
    不吊线谷歌
    ©2008 MyExt.cn
    本站文章来自互联网,仅供学习和研究使用,如有版权问题,请发送Email:myext@126.com.