前言
甘露模型是用于开发基于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. 将worker的prototype的隐式链指向person的prototype对象
3. 将worker类的方法都挂在worker的prototype对象上
通过这三步我们就实现了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中的类,其实例化对象只能调用该类中this和prototype上的属性及方法,而通过类名直接增加的属性和方法是与该类的实例化对象无关的,也就是说,实例化对象无法调用通过类名直接增加的属性和方法”,这个说起来好像很复杂,下面做个测试:
<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.age是undefined,而test.age则是上面所赋值的23。正和我上面归纳的一样,而原因是什么呢?在我苦思冥想后终于开朗,原因是这样的:test作为一个函数对象,分为内外两部分,内的就是{}中间的部分,其余的都是其外边的部分包括prototype也是外边的部分。当我们使用 var t = new test();时,首先是创建了一个新的空的对象,然后将这个空的对象作为test的this调用,这样t就拥有了test内的所有东东,然后将t的隐式链指向test的prototype,这样又拥有了test.prototype上的东东,但是,test外边的东西,除了test.prototype外,其余的东东都没有付给这个t对象,所以,t无法访问到test外的除test.prototype外的东东。
好了,插了这么大一段“废话”,目的就是要说明。想通过this.base来直接调用baseClass——没门,那是否要放弃这种方式呢?可以明确的告诉你,不用。有一个方法是可以解决,但这个方法在逻辑上是相当的绕,各位先理理脑筋,免得等下打结。
this,也就代表当前的实例化出来的对象,而上面说得很清楚了,这个对象是访问不到我们所添加的base的,但是this访问不到,他会根据继承关系一层层的往下找,先找person然后找Function再找Object,如果还没有那就真是没有了。
这样的话,我们可以在Function或Object的prototype上加个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>
至此,我们基本完成了甘露模型的构建,现在体会一下,准备用它大展用途吧!
本文来自CSDN博客,转载请标明出处:
转载于:https://blog.51cto.com/azurelover/571880