再读《悟透javascript》之三、甘露模型

前言

      甘露模型是用于开发基于javascript的类库的,通过它,我们可以以类似C#等面向对象的语言式的模式来开发javascript类库,这将使你的javascript代码变得清晰有条理也便于维护。

 

 

一、创建对象三部曲

前面有说过,使用new来创建一个对象,可以分为三个步骤,比如:var x = new obj();

1.       创建一个空对象,var x = {};,注意,即使x指针原来指向某个对象,此时也会被新创建的空对象替换,也就是改变了x的指向。

2.       将新创建对象的隐式链指向obj对象的prototype对象,x.__proto__=obj.prototype;,此时,新创建的对象就可以使用obj.prototype上的属性和方法了。

3.       将新创建的对象作为obj函数的this来调用,obj.call(this);,这样,新创建的对象就拥有了obj函数中的所有this属性。

 

 

二、继承

我们再来回顾一下,javascript类的继承,如下:

function person(name, age){

        this.name = name;

        this.age = age;

    }

    person.prototype.say = function(){alert(this.name+" is "+this.age+" years old");}

   

    function worker(name, age, salary){

        person.apply(this, [name, age]);

        this.salary = salary;

    }

worker.prototype = new person();

worker.prototype.show = function(){alert(this.name+"'s salary is "+this.salary);}

这里worker类继承了person类,我们来仔细分析worker是如何继承person的。我发现这里也可以分为三步:

1.       是构造函数初始化this,这里的初始化还包括调用基类的构造函数来初始化基类的this

2.       workerprototype的隐式链指向personprototype对象

3.       worker类的方法都挂在workerprototype对象上

通过这三步我们就实现了worker类继承person类。那么我们可否在一个函数中,封装这三个步骤呢?当然可以,这里需要使用到前面提到的语法甘露。

 

 

三、初步的甘露模型

要封装继承的实现,我们必须先找出它的要素,这是继承,所以必须要有一个基类,其次是要定义的类。这样我们就可以得到如下代码:

    function Class(){

        var defineClass = arguments[arguments.length-1];

        if(!defineClass) return;

       

        var baseClass = arguments.length > 1 ? arguments[0] : Function;

        if(!baseClass) return;

    }

      基类和要定义的类都是必须有的,否则无法继续,但是基类可以空缺,空缺的话就用Function对象替代,Function是所有函数的基类。

 

 

      然后我们按上面分析的显示继承的三步来走。

 

 

      一、我们要初始化this,这必须要有一个构造函数,我们假定每个对象都有一个同名的构造函数create,这个create其实就是我们所说的类。所有又有了如下代码:

    function Class(){

        var defineClass = arguments[arguments.length-1];

        if(!defineClass) return;

       

        var baseClass = arguments.length > 1 ? arguments[0] : Function;

        if(!baseClass) return;

       

//声明的类即是构造函数,如果缺省的话使用默认构造函数

        var _class = defineClass.create ? defineClass.create : function(){}   

}

_class就是类的构造函数,如果缺省的话,会默认为一个空的函数。按上面的说法,构造函数中应该有调用基类的构造函数的语句才对,但是此处的基类是Function,又没有参数,所以就为空了。

 

 

      二、我们要将定义类的prototype的隐式链指向基类的prototype,结果如下:

    function Class(){

        var defineClass = arguments[arguments.length-1];

        if(!defineClass) return;

       

        var baseClass = arguments.length > 1 ? arguments[0] : Function;

        if(!baseClass) return;

       

               //声明的类既是构造函数,如果缺省的话使用默认构造函数

        var _class = defineClass.create ? defineClass.create : function(){}

       

        function _prototype(){}

        _prototype.prototype = baseClass.prototype;

        var prototype = new _prototype();    //构造声明类的prototype

          

        _class.prototype = prototype;

}

这里使用了一个_prototype壳函数来避免直接new baseClass,因为直接new baseClass中可能出现很多的程序副本,这这些副本是不必要的累赘。

 

 

      三、将定义类的方法挂到定义类的prototype上,代码如下:

    function Class(){

        var defineClass = arguments[arguments.length-1];

        if(!defineClass) return;

       

        var baseClass = arguments.length > 1 ? arguments[0] : Function;

        if(!baseClass) return;

       

               //声明的类既是构造函数,如果缺省的话使用默认构造函数

        var _class = defineClass.create ? defineClass.create : function(){}

       

        function _prototype(){}

        _prototype.prototype = baseClass.prototype;

        var prototype = new _prototype();    //构造声明类的prototype

       

        for(var member in defineClass)  //将定义的类的元素复制到声明类的prototype

            if(member!="create")

                prototype[member] = defineClass[member];

          

        _class.prototype = prototype;

 

        return _class;

}

因为create是用来作为构造函数的,所以,在将定义类的方法挂到定义类的prototype上时,不需要将create挂上去。完成了这第三步,我们也就完成了对继承的三步的封装,最后将_class构造函数返回。

 

 

      四、实际应用一下,例:

    var person = Class({

        create:function(name, age){

            this.name = name;

            this.age = age;

        },

        say:function(){

            alert(this.name+" is "+this.age+" years old");

        }

    });

   

    var p = new person("soldierluo", 23);

    p.say();

   

    var worker = Class(person, {

        create:function(name, age, salary){

            person.apply(this, [name, age]);

            this.salary = salary;

        },

        show:function(){

            alert(this.name+"'s salary is "+this.salary+"$");

        }

    });

   

    var w = new worker("luo", 33, 33333);

    w.say();

w.show();

这里,我们成功的进行了类的声明、继承、实例化和调用,并且结构清晰。这已经很好了吧,确实,看起来是没什么问题了——————但是,还可以更好。上面,在worker的构造函数中需要调用基类的构造函数来初始化this,使用person.apply(this, [name, age]);的方式并非很直接,加入我们可以这样this.base(…);,不是更好了吗?(base是基类,也就是基类构造函数)

 

 

四、完善后的甘露模型

上面说到的使用基类名称来初始化this,并不是很直接。如果改成this.base(…)的法师会好很多,如果这样的话,我们就需要为构造函数添加一个base的基类属性,如下:

    function Class(){

        var defineClass = arguments[arguments.length-1];

        if(!defineClass) return;

       

        var baseClass = arguments.length > 1 ? arguments[0] : Function;

        if(!baseClass) return;  

       

        var _class = defineClass.create ? defineClass.create : function(){}

       

        function _prototype(){}

        _prototype.prototype = baseClass.prototype;

        var prototype = new _prototype();    //构造声明类的prototype

       

        for(var member in defineClass)  //将定义的类的元素复制到声明类的prototype

            if(member!="create")

                prototype[member] = defineClass[member];

       

        _class.prototype = prototype;

        _class.base = baseClass;

       

        return _class;

}

然后,我们将应用的代码改成

    var person = Class({

        create:function(name, age){

            this.name = name;

            this.age = age;

        },

        say:function(){

            alert(this.name+" is "+this.age+" years old");

        }

    });

   

    var worker = Class(person, {

        create:function(name, age, salary){

            this.base(name, age);

            this.salary = salary;

        },

        show:function(){

            alert(this.name+"'s salary is "+this.salary+"$");

        }

    });

   

    var w = new worker("luo", 33, 33333);

    w.say();

    w.show();

执行后发现,居然报错了,错误是“对象不支持此属性或方法”。哪个对象不支持哪个属性或方法?我们刚才仅仅是将person.apply(this, [name, age]);改成了this.base(name, age);难道问题出在这?改回来再试试,果然又可以了。

 

 

      看来问题的确出在this.base这里,那我们检查一下这个this.base吧!用alert(this.base);后发现,this.base居然是undefined。这怎么可能,上面我们不是已经为_class增加了base属性吗?

 

 

这是为什么,我试来试去后发现,“对于javascript中的类,其实例化对象只能调用该类中thisprototype上的属性及方法,而通过类名直接增加的属性和方法是与该类的实例化对象无关的,也就是说,实例化对象无法调用通过类名直接增加的属性和方法”,这个说起来好像很复杂,下面做个测试:

    <script type="text/javascript">

    function test(){

        this.name="ddd";

    }

    test.age = 23;

   

    var t = new test();

   

    alert(t.name+":"+t.age+":"+test.age);

</script>

结果发现,t.ageundefined,而test.age则是上面所赋值的23。正和我上面归纳的一样,而原因是什么呢?在我苦思冥想后终于开朗,原因是这样的:test作为一个函数对象,分为内外两部分,内的就是{}中间的部分,其余的都是其外边的部分包括prototype也是外边的部分。当我们使用 var t = new test();时,首先是创建了一个新的空的对象,然后将这个空的对象作为testthis调用,这样t就拥有了test内的所有东东,然后将t的隐式链指向testprototype,这样又拥有了test.prototype上的东东,但是,test外边的东西,除了test.prototype外,其余的东东都没有付给这个t对象,所以,t无法访问到test外的除test.prototype外的东东。

 

 

好了,插了这么大一段“废话”,目的就是要说明。想通过this.base来直接调用baseClass——没门,那是否要放弃这种方式呢?可以明确的告诉你,不用。有一个方法是可以解决,但这个方法在逻辑上是相当的绕,各位先理理脑筋,免得等下打结。

 

 

this,也就代表当前的实例化出来的对象,而上面说得很清楚了,这个对象是访问不到我们所添加的base的,但是this访问不到,他会根据继承关系一层层的往下找,先找person然后找Function再找Object,如果还没有那就真是没有了。

这样的话,我们可以在FunctionObjectprototype上加个base函数,在这个base函数中,我们可以通过caller知道谁调用了这个base,而这个caller恰好就是create函数,也就是Class中的_class,这样我们绕了个圈又找回了create函数对象,现在我们就可以放心的调用_class.base(…)了,代码如下:

    Function.prototype.base = function(){   //调用基类的构造函数

        var Caller = Function.prototype.base.caller;

        Caller&&Caller.base&Caller.base.apply(this, arguments);

    }

 

 

五、最终的代码及示例如下

    <script type="text/javascript">

    function Class(){

        var defineClass = arguments[arguments.length-1];

        if(!defineClass) return;

       

        var baseClass = arguments.length > 1 ? arguments[0] : Function;

        if(!baseClass) return;

       

        var _class = defineClass.create ? defineClass.create : function(){}  

       

        function _prototype(){}

        _prototype.prototype = baseClass.prototype;

        var prototype = new _prototype();    //构造声明类的prototype

       

        for(var member in defineClass)  //将定义的类的元素复制到声明类的prototype

            if(member!="create")

                prototype[member] = defineClass[member];

       

        _class.prototype = prototype;

        _class.base = baseClass;

       

        return _class;

    }

   

    Function.prototype.base = function(){   //调用基类的构造函数

        var Caller = Function.prototype.base.caller;

        Caller&&Caller.base&Caller.base.apply(this, arguments);

    }

   

    var person = Class({

        create:function(name, age){alert(this.base);

            this.base();

            this.name = name;

            this.age = age;

        },

        say:function(){

            alert(this.name+" is "+this.age+" years old");

        }

    });

   

    var p = new person("soldierluo", 23);

    p.say();

   

    var worker = Class(person, {

        create:function(name, age, salary){alert(this.base);

            this.base(name, age);

            this.salary = salary;

        },

        show:function(){

            alert(this.name+"'s salary is "+this.salary+"$");

        }

    });

   

    var w = new worker("luo", 33, 33333);

    alert(w.constructor);

    w.say();

    w.show();

    </script>

至此,我们基本完成了甘露模型的构建,现在体会一下,准备用它大展用途吧!

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值