prototype.js 源码学习笔记(二)

  这次学习了prototype.js的Class对象,相当于模拟类的创建,以下是代码:

代码
var Class = (function() {
//临时中转函数
function subclass() {};
//创建
function create() {
var parent = null, properties = $A(arguments);
//检查传入的第一个参数是否为Function类型
if (Object.isFunction(properties[0]))
//如果是则取出这个函数为基类
parent = properties.shift();
//新类
function klass() {
this.initialize.apply(this, arguments);
}
//复制为klass的静态属性
Object.extend(klass, Class.Methods);
//设置klass的父类引用和派生列表
klass.superclass = parent;
klass.subclasses
= [];
//如果父类存在
if (parent) {
//将临时中转函数的原型指向父类的原型对象
subclass.prototype = parent.prototype;
//将klass的原型指向实例化以后的中转函数对象
klass.prototype = new subclass;
//并将klass添加到父类的派生列表当中
parent.subclasses.push(klass);
}
//其余的参数都为object,用来进行方法扩展,依次遍历它们
for (var i = 0; i < properties.length; i++)
klass.addMethods(properties[i]);
//如果继承的父类没有构造函数,则自定添加一个默认的空构造函数
if (!klass.prototype.initialize)
klass.prototype.initialize
= Prototype.emptyFunction;
//重新设置klass的构造器引用
klass.prototype.constructor = klass;
return klass;
}
//扩展当前类的方法
function addMethods(source) {
var ancestor = this.superclass && this.superclass.prototype;
//取出传入参数的键
var properties = Object.keys(source);
//ie对自定义toString和valueOf也做了同Object默认方法同样的处理,无法用for...in遍历
if (!Object.keys({ toString: true }).length) {
if (source.toString != Object.prototype.toString)
properties.push(
"toString");
if (source.valueOf != Object.prototype.valueOf)
properties.push(
"valueOf");
}
//遍历要扩展的方法
for (var i = 0, length = properties.length; i < length; i++) {
var property = properties[i], value = source[property];
//通过第一个参数为"$super"关键字,引用同名的超类方法
if (ancestor && Object.isFunction(value) &&
value.argumentNames().first()
== "$super") {
//用一个变量临时保存当前要扩展的方法
var method = value;
//将超类的同名方法的指针指向这个传入的扩展对象,并以第一个参数的形式传入到要扩展的方法当中
value = (function(m) {
return function() { return ancestor[m].apply(this, arguments); };
})(property).wrap(method);
//由于执行wrap返回的是一个新的函数,所以重新设置一下原方法的toString和valueOf
value.valueOf = method.valueOf.bind(method);
value.toString
= method.toString.bind(method);
}
//将方法添加到要扩展的类的原型对象当中
this.prototype[property] = value;
}

return this;
}

return {
create: create,
Methods: {
addMethods: addMethods
}
};
})();

   看到很多朋友自己写代码的时候都是使用的这种创建方式,好像1.6对之前的Class进行了扩展,比如supperclass,subclasses这些静态属性。还是一点一点的分析吧。

  首先它执行了一个Class的function并返回一个object字面量,这样做的好处就是通过闭包把内部的create方法和addMethods方法很好的保护起来,以实现"私有"的概念。返回的对象分别引用着这两个私有方法。

  create顾名思义是创建一个构造器,也就是"类"。它的用法是这样的   var myClass = Class.create("超类",{要扩展的方法});   首先它先用properties将传进来的实参进行了array类型的转换,紧接着对实参的第一个参数进行了判断,如果是Function类型的,证明会进行对超类的继承,所以取出第一个参数付给parent变量,紧接着创建一个名为Klass的函数,这个函数是要作为最后返回的新类进行处理的,也就是我们最终创建的对象。所以接下来主要都是针对这个klass进行操作。先是将Class的私有addMethods方法扩展给它,以便让它拥有方法扩展的功能。紧接着又为它指定了两个静态属性,supperclass-储存着对超类的引用,subclasses-储存着对自身扩展出来子类集合的引用。再通过判断parent来得之是否要进行类继承,如果需要继承,这里就用到了function subclass() {};这个临时的中转函数。先将这个中转函数的原型对象的引用,指向要被继承的超类的原型对象,由于prototype.js所有类的方法都是通过prototype方式创建的,所以这样就得到了对超类原型链上所有方法的使用权。接着将这个中转函数进行了实例化并作为klass的原型对象,所以klass也就通过原型链拥有了超类的所有方法,为什么要设置一个中转函数哪?因为原型对象都是引用类型的,如果直接将klass的prototype指向超类的prototype,那么今后对klass的原型方法进行修改,也会造成对其超类的同名方法的改变。而实例化这个中转函数以后,klass的prototype与超类原型上引用的就已不再是同一对象,可以通过添加新的方法来遮挡原型链上的同名方法,以实现对继承来的方法的"重写"。接着,通过遍历除超类以外的参数,来进行方法的扩展 

for (var i = 0; i < properties.length; i++)
klass.addMethods(properties[i]);

 

对方法扩展这块一会再说,继续向下,紧接着又检查了klass原型链上是否存在initialize这个固定名称的构造函数,如果没有,则自动创建一个空的构造函数,最后设置了一下klass的构造器并将klass返回klass.prototype.constructor = klass;  说实话这里我不明白为什么要这么做。。。。。。不设置这个感觉更合理些,这样导致了这种情况  

var a=Class.create();
var b=new a();
alert(b.constructor);
//function klass(){.....}
alert(b.constructor==klass); // error 'klass未定义'

希望了解情况的大虾能指点一下......

  接下来分析一下addMethods方法。这个方法专门提供方法的扩展。它首先获得当前要扩展对象的supperclass属性的原型对象。然后对传入的参数source(肯定是一个对象字面量)进行了操作,将这个对象字面量的键值通过 var properties = Object.keys(source); 组成数组赋给了properties。然后在后面通过循环来遍历它的属性。但是这里为什么不直接用for...in哪,我推测应该和下面这一段代码有关

 

代码
if (!Object.keys({ toString: true }).length) {
if (source.toString != Object.prototype.toString)
properties.push(
"toString");
if (source.valueOf != Object.prototype.valueOf)
properties.push(
"valueOf");
}

经过测试发现,在ie下为对象赋自定义属性toString和valueOf,在for...in时处理的结果和对象本身自带的toString和valueOf结果是一样的,都无法访问到,所有在这里prototype.js做了这样一个巧妙的设置,将自定义的toString和valueOf添加到properties集合当中,再通过for循环对其进行处理。在处理每一个要扩展的属性的时候,我遇到了prototype.js很有意思的一个设置-"$super"。这个关键字可以作为要扩展方法的第一个参数出现,一但要扩展的方法出现了这个关键字,那么这个方法内部就可以通过$super来取得超类上与之同名的方法的引用。如果没有接触过的童鞋,对这个概念理解起来有些困难,可以参考一下prototype.js的api文档,我当时也看了半天才明白过来怎么回事。。。。

 

代码
if (ancestor && Object.isFunction(value) &&
value.argumentNames().first()
== "$super") {
var method = value;
value
= (function(m) {
return function() { return ancestor[m].apply(this, arguments); };
})(property).wrap(method);

value.valueOf
= method.valueOf.bind(method);
value.toString
= method.toString.bind(method);
}

开始曾被这部分的指针搞得有点晕,还有就是wrap方法,这个方法放到后面再研究,它的参数应该传入一个function,大体是这样用的,把调用它的函数或方法传递给作为它参数传入的function的第一个参数使用,并返回一个新的函数。听着有点乱吧。。。。。。这个方法感觉就是为"$super"这个特性准备的。这段主要就是通过argumentNames获取方法的形参,判断是否有"$super"关键字。然后为$super引用超类的同名方法并最终通过this.prototype[property] = value;将指针指向实例化以后的对象。valueOf和toString这块的设置是因为wrap返回了一个新的函数所以value调用它们不能返回有效的值,所以把method的toString和valueOf定义给了value,因为value是从method修改而来的,所以代码结构上几乎是一致的。最后return了this应该是可以实现链式调用吧。

  大概理解就是这样了,下次继续。 

 

posted @ 2009-12-16 13:20  黄金小强  阅读(1354)  评论(2编辑  收藏  举报