看了一天prototype 源码,其实根本没想看的,才刚碰了1天啊,可是有些地方就是想知道他怎么实现的,
又或者有些结果不是自己想象的那样,忍不住还是拿firebug追踪了一下,虽然看的一头雾水,好歹有点收获,这种行为就是浪费时间啊。。。。时间过的好快!一会儿一天就过去了。记录一下,说的很乱,因为还不是非常理解。
且看一点理解一点,理解一点记录一点,最后再去整理吧,不知要等到何时呀~
1 页面加载时,.<script type="text/javascript" src="prototype-1.6.0.3.js"></script> 首先被加载,
js引擎先解析其中定义的变量,也就是 var xxx = .... 的语句。
然后返回头来一句一句执行。定义了各个对象以及工具函数, 几个重点看过的对象/方法有:
( 写法不标准,方便写而已,源码中可要规范多了)
Object.extend(A,B) 实现A继承B的属性方法.,就是一个拷贝属性的函数
Element : 自定义的window.Element , 继承了浏览器自带的Element对象,并用Element.Methods里面的方法进行了扩展。在扩展的构造函数中,对各个扩展方法也进行了methodize化(下面有说)。
其实在prototype中我们使用页面元素对象的情况大多就两种:
1. var e = new Element(tagName);
2 var e = $('ID')
这两种方式有什么异同呢? 对使用来说,第一种用于创建一个元素对象,就像document.createElement一样,但是该对象支持prototype的扩展方法。 第二种用于获取页面中的一个元素,参数ID自然就是表示元素的id了(也可以用别的,文档中有).
重要的是,二者的实现过程。今天看了下源码,大体理了下思路:
1 $() 方法:
function $(element) {
if (arguments.length > 1) { //这一段使用一个递归,来完成多个参数的处理
for (var i = 0, elements = [], length = arguments.length; i < length; i++)
elements.push($(arguments[i]));
return elements;
}
if (Object.isString(element)) //如果传入的是字符串(ID), 先以DOM API获取该元素
element = document.getElementById(element);
return Element.extend(element); //然后对该元素进行扩展, 用到了Element.extend()方法
}
而Element就稍微复杂一些,因为不同浏览器实现不同,有的内置Element对象(Firefox),有的没有(IE),作者写了一个函数来解决:
// 将window.Element 扩展,使用链式构造函数
(function() {
var element = this.Element; //保存原来的window.Element
this.Element = function(tagName, attributes) { //构造函数
attributes = attributes || { };
tagName = tagName.toLowerCase();
var cache = Element.cache; //该对象用来缓存已经创建的指定tagName的对象,用于快速创建新的指定tagName的对象 //下面会有用法: 使用clone快速从cache里面复制对象,而不用再次document.createElement
//有些元素,xhtml标准是不允许有name属性的,但是IE允许,而且可以用下面的方式使用createElement函数(因为不同浏览器
//对API的实现不同), 因此如果属性中给出了name属性且是ie,就满足用户的需求.既然有name,那么自然也不用缓存了
//因为不可重用啊,name理论上应该是唯一的!
if (Prototype.Browser.IE && attributes.name) {
tagName = '<' + tagName + ' name="' + attributes.name + '">';
delete attributes.name;
//写入属性并返回Element对象
return Element.writeAttribute(document.createElement(tagName), attributes);
}
//如果没有缓存过该对象,则创建并缓存它,比如cache["div"]=document.createElement("div")
if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
//使用cloneNode 从缓存中赋值对象,然后写入属性参数,返回该对象,完成创建
return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
};
Object.extend(this.Element, element || { }); //将原来的Element属性加载到新Element
if (element) this.Element.prototype = element.prototype;//加载旧的prototype
}).call(window); // 以window对象的身份调用该函数, 将Element类附加到window的属性中,完成扩展.
在后面的代码中,对Element类附加了一些方法,自然包括Element.Methods啦。
// 将Element.Methods 赋给Element类.
Object.extend(Element, Element.Methods);
....
//某些浏览器,比如IE,是没有该方法的,因此要进行扩展
Element.hasAttribute = function(element, attribute) {
if (element.hasAttribute) return element.hasAttribute(attribute);
return Element.Methods.Simulated.hasAttribute(element, attribute);
};
此外还有一些方法,暂时还没看,都是以 Element.functionName = function(){..} 形式定义的。
另一个重要方法是Element.extend, 它是完成元素对象扩展的重头戏:
Element.extend(x)
//很有意思的写法, Element.extend 指向的函数决定于一个条件:
// if (Prototype.BrowserFeatures.SpecificElementExtensions),
//如果为真,则extend = k(x), 否则就是一个完成扩展功能的函数.
Element.extend = (function() {
if (Prototype.BrowserFeatures.SpecificElementExtensions)
return Prototype.K;
// 返回一个function,其功能是对参数对象进行扩展,主要是将 Element.Methods 中的方法附加到对象上
}
Element.Methods 对象,包含了各个扩展方法, 其各个方法都有个elem参数,也就是调用该方法的元素对象。而实际代码中,调用的时候是不传入该参数的,比如 函数定义:
update(elem, argument); 用一个div元素对象去调用它 mydiv.update(argument), 不传入mydiv这个对象参数。 实际作者是这么做的:
首先对Fouction 类的原型做扩展,加上methodize方法,在对 Element对象扩展的时候,当要把Element.Methods 中的方法加到 elem 对象上,是这样做的: 不然函数名是 update,
element["update"] = update.methodize(); 这个methodize() 的作用就是:
返回一个函数: function(){
return update.apply (null, [this].concat(arguments));
}
也就是如果调用 div.update(arg), 实际调用的是 update.apply(this, [div,arg..]) ,也就符合了 Methods中的定义 update(elem,arguments);
因此这些扩展函数有三种调用方法:
1. $(元素ID).update(参数);
2. Element.update(元素ID, 参数); // prototype初始化的时候将扩展函数都继承到Element类了,而且是没有经过methodize()函数包装的"原装方法"。
3. var e = new Element('div'); e.update(参数); 类似方式1.
至于作者这样做的原因, 开始我以为直接调用会造成this 对象丢失的情况,也就是 div.update(args) , 在执行过程中this并不指向div对象,但测试下来没有这个问题。 因此我认为,作者是为了给用户一个方便的调用方式。 毕竟 div.update(args) 比 div.update(div, args) 要清晰、方便一些。
看代码过程中的一些注解
//很有意思的写法, Element.extend 指向的函数决定于一个条件:
// if (Prototype.BrowserFeatures.SpecificElementExtensions),
//如果为真,则extend = k(x), 否则就是一个完成扩展功能的函数.
//该方法将传入elem对象扩展后返回新对象.
// 看了半天才看懂, NND..
//不返回K的情况(IE测试,FF不会执行这一段):
//function(){
//定义Methods = {}
// 局部变量extend = Object.extend(function(element),refresh());
// 调用extend.refresh(); 就用 Element.Methods,Element.Methods.Simulated
//填充了Methods,然后返回extend.
//}, 然后function() 调用. 返回extend代表的函数
//注意,这里使用了js里面的闭包特性, 我们关注那个var Methods = { }对象,
//extend方法中,var methods = Object.clone(Methods) 这一句拷贝了Methods中的内容.而实际调用extend的
//是外部元素Element.也就是在外部function(即Element.extend = (function() 中那个function)之外调用,而Methods是一个私有变量,它的值是外部函数返回时的值
//,但也会受到内部函数的影响。
//即,当调用了外部函数时,默认执行了一个refresh, 然后返回extend.此时的Methods就是refresh过后的,那么extend内也会用到这些内容.
//如果以后再次调用refresh(), Methods依然存在,虽然它是在外部函数中,并且会得到更新,再次调用extend,它引用的Methods内容也会
//更新.
//不过我觉得,这里不用闭包这种抽象的概念也可以完成,呵呵,大概是为了便于管理:)
//关于扩展函数: 一共有两个来源, 一个是 Element.Methods,一个是Element.Methods.ByTag[]
//其中Element.Methods中的方法是对各个元素通用的, 而Element.Methods.ByTag[] 是针对特定标签元素的
//在后续的Element.AddMethods 方法中, 会根据AddMethods的参数来决定是通用方法还是面向特定元素的方法
//然后向Element.Methods 或者 Element.Methods.ByTag填充方法.
//最后,调用Element.extend.refresh() 将新的方法填充到Methods对象.因为extend方法是从Methods对象中取方法,
//而refresh方法是向Methods对象中填充方法.
Element.extend = (function() {
if (Prototype.BrowserFeatures.SpecificElementExtensions)
return Prototype.K;
var Methods = { }, ByTag = Element.Methods.ByTag;
var extend = Object.extend(function(element) {
if (!element || element._extendedByPrototype ||
element.nodeType != 1 || element == window) return element;
//看到没有,这里methods 数据来自Methods,而Methods由refresh()决定
var methods = Object.clone(Methods),
tagName = element.tagName.toUpperCase(), property, value;
//对于指定的标签,将指定标签扩展方法加载到methods中
// extend methods for specific tags
if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
//进行扩展,这里使用了Function.prototype的methodize(),methidze是将调用者附加到方法参数中.
for (property in methods) {
value = methods[property];
if (Object.isFunction(value) && !(property in element))
{
//alert("DEBUG: Mtheods.value: "+value);
element[property] = value.methodize();
}
}
element._extendedByPrototype = Prototype.emptyFunction;
return element;
}, {
refresh: function() {
// extend methods for all tags (Safari doesn't need this)//我想是因为闭包实现不同的原因?
if (!Prototype.BrowserFeatures.ElementExtensions) {
Object.extend(Methods, Element.Methods);
Object.extend(Methods, Element.Methods.Simulated);
}
}
});
extend.refresh();//填充Methods对象
return extend; //返回内部的扩展函数
})();
// 该对象包含的方法是模拟浏览器原生的一些方法,
//比如hasAttribute, IE就没有, 而Firefox原生内置该方法.
//当一个元素被ELement.extend扩展后,就具有了该能力
Element.Methods.Simulated = {
hasAttribute: function(element, attribute) {
attribute = Element._attributeTranslations.has[attribute] || attribute;
var node = $(element).getAttributeNode(attribute);
return !!(node && node.specified);
}
};
还有一个方法,Elment.AddMethods
给Element添加方法。其工作过程涉及Element.Methods 和Element.Methods.ByTag
Element.Methods 是对所有元素要扩展的方法集合,ByTag是对特定元素的一些附加方法
Element.addMethods = function(methods) {
//注意T 是对对象的引用哦,开始脑袋晕了,居然当成值传递了,导致下面的代码感觉有问题-_-!
var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
//如果无参数,则扩展特殊元素的方法
if (!methods) {
Object.extend(Form, Form.Methods);
Object.extend(Form.Element, Form.Element.Methods);
Object.extend(Element.Methods.ByTag, { //维护扩展函的数组(以tagName索引)
"FORM": Object.clone(Form.Methods),
"INPUT": Object.clone(Form.Element.Methods),
"SELECT": Object.clone(Form.Element.Methods),
"TEXTAREA": Object.clone(Form.Element.Methods)
});
}
if (arguments.length == 2) {
var tagName = methods; //arguments[0],Array or Object
methods = arguments[1];//the 2nd argument which is the method to be added
}
//如果参数不是2,即不是针对某个tag的,则扩展到Methods.
if (!tagName) Object.extend(Element.Methods, methods || { });
else {//否则对参数中每个tag进行继承扩展
if (Object.isArray(tagName)) tagName.each(extend);
else extend(tagName);
}
//将函数附加到ByTag[]数组
function extend(tagName) {
tagName = tagName.toUpperCase();
if (!Element.Methods.ByTag[tagName])
Element.Methods.ByTag[tagName] = { };
Object.extend(Element.Methods.ByTag[tagName], methods); //将方法添加到特定的tag元素上
}
//拷贝方法,用到了methodize
function copy(methods, destination, onlyIfAbsent) {
onlyIfAbsent = onlyIfAbsent || false;
for (var property in methods) {
var value = methods[property];
if (!Object.isFunction(value)) continue;
if (!onlyIfAbsent || !(property in destination))
destination[property] = value.methodize();
}
}
//返回DOM对象的类,以tagName做元素索引
function findDOMClass(tagName) {
var klass;
var trans = {
"OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
"FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
"DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
"H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
"INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
"TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
"TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
"TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
"FrameSet", "IFRAME": "IFrame"
};
if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
if (window[klass]) return window[klass];
klass = 'HTML' + tagName + 'Element';
if (window[klass]) return window[klass];
klass = 'HTML' + tagName.capitalize() + 'Element';
if (window[klass]) return window[klass];
window[klass] = { };
window[klass].prototype = document.createElement(tagName)['__proto__'];
return window[klass];
}
//我要说说这两个条件:
//F.ElementExtensions :我认为是判断浏览器是否有内置元素对象
//比如IE 输出div对象的时候,只有Object, 而Firefox会输出HTMLdivElement,FF对其进行了区分,这就是不同
//F.SpecificElementExtensions 类似,是支持一些特殊的元素扩展
//这两个对象在Prototype对象中有描述.
//事实上, Prototype库采用了这样创建/扩展元素的方法:
//对于不符合这些条件的浏览器,创建/获取/扩展对象的时候,都用Element.extend将对象进行扩展.
//否则,就用下面这两段代码进行扩展,直接扩展到元素的原型上.
//在使用$() 来获取元素对象的时候,会调用Element.extend()方法, 而Element.extend内部,会根据是否符合这两个
//条件,决定是直接返回对象(因为已经在下面对原型进行了扩展,不需要再扩展了),还是扩展后返回。
//当然,支持这些条件的浏览器会有较高的效率,因为每次获取不同的元素,不需要再次扩展了,这也是原型扩展的好处
//如果浏览器内置了那些那些对象,比如Firefox中的window.HTMLdivElement
//就直接将扩展函数拷贝给HTMLElement
if (F.ElementExtensions) {
copy(Element.Methods, HTMLElement.prototype);
copy(Element.Methods.Simulated, HTMLElement.prototype, true);
}
//同上, 满足条件则把对特定标签的扩展方法直接加到对象的原型上
if (F.SpecificElementExtensions) {
for (var tag in Element.Methods.ByTag) {
var klass = findDOMClass(tag);
if (Object.isUndefined(klass)) continue;
copy(T[tag], klass.prototype);
}
}
//alert("Extended " + counter++);
//将更新后的Element.Methods附加到Element类
Object.extend(Element, Element.Methods);
delete Element.ByTag;//上一句也将ByTag附加给Element类了,去掉它,因为Element不需要ByTag
if (Element.extend.refresh) Element.extend.refresh();
Element.cache = { };//既然上面将Element类更新了(添加了新方法),那么自然要清空缓存,因为缓存的对象都是未更新之前
//的Element类的对象,不支持新方法.
};