prototype 源码中文说明之 prototype.js
2018-09-27 09:43
/**
*定义一个全局对象,属性Version在发布的时候会替换为当前版本号
*/
varPrototype={
Version:@@VERSION@@
}
/**
*创建一种类型,注意其属性create是一个方法,返回一个构造函数。
*一般使用如下
*varX=Class.create();返回一个类型,类似于java的一个Class实例。
*要使用X类型,需继续用newX()来获取一个实例,如同java的Class.newInstance()方法。
*
*返回的构造函数会执行名为initialize的方法,initialize是Ruby对象的构造器方法名字。
*此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法。
*
*如果一定要从java上去理解。你可以理解为用Class.create()创建一个继承g.Class类的类。当然java不允许这样做,因为Class类是final的
*
*/
varClass={
create:function(){
returnfunction(){
}
}
}
/**
*创建一个对象,从变量名来思考,本意也许是定义一个抽象类,以后创建新对象都extend它。
*但从其后代码的应用来看,Abstract更多是为了保持命名空间清晰的考虑。
*也就是说,我们可以给Abstract这个对象实例添加新的对象定义。
*
*从java去理解,就是动态给一个对象创建内部类。
*/
varAbstract=newObject();
/**
*获取参数对象的所有属性和方法,有点象多重继承。但是这种继承是动态获得的。
*如:
*vara=newObjectA(),b=newObjectB();
*varc=a.extend(b);
*此时c对象同时拥有a和b对象的属性和方法。但是与多重继承不同的是,cinstanceofObjectB将返回false。
*/
Object.prototype.extend=function(object){
for(propertyinobject){
this[property]=object[property];
}
returnthis;
}
/**
*这个方法很有趣,它封装一个javascript函数对象,返回一个新函数对象,新函数对象的主体和原对象相同,但是bind()方法参数将被用作当前对象的对象。
*也就是说新函数中的this引用被改变为参数提供的对象。
*比如:
*<inputtype=textid=aaavalue=aaa>
*<inputtype=textid=bbbvalue=bbb>
*.................
*<script>
*varaaa=document.getElementById(aaa);
*varbbb=document.getElementById(bbb);
*aaa.showValue=function(){alert(this.value);}
*aaa.showValue2=aaa.showValue.bind(bbb);
*</script>
*那么,调用aaa.showValue将返回aaa,但调用aaa.showValue2将返回bbb。
*
*apply是ie5.5后才出现的新方法(Netscape好像很早就支持了)。
*该方法更多的资料参考MSDN
*还有一个call方法,应用起来和apply类似。可以一起研究下。
*/
Function.prototype.bind=function(object){
varmethod=this;
returnfunction(){
method.apply(object,arguments);
}
}
/**
*和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象
*注意这时候,用到了Function.call。它与Function.apply的不同好像仅仅是对参数形式的定义。
*如同java两个过载的方法。
*/
Function.prototype.bindAsEventListener=function(object){
varmethod=this;
returnfunction(event){
method.call(object,eventwindow.event);
}
}
/**
*将整数形式RGB颜色值转换为HEX形式
*/
Number.prototype.toColorPart=function(){
vardigits=this.toString(16);
if(this<16)return0+digits;
returndigits;
}
/**
*典型Ruby风格的函数,将参数中的方法逐个调用,返回第一个成功执行的方法的返回值
*/
varTry={
these:function(){
varreturnValue;
for(vari=0;i<arguments.length;i++){
varlambda=arguments[i];
try{
returnValue=lambda();
break;
}catch(e){}
}
returnreturnValue;
}
}
/*--------------------------------------------------------------------------*/
/**
*一个设计精巧的定时执行器
*首先由Class.create()创建一个PeriodicalExecuter类型,
*然后用对象直接量的语法形式设置原型。
*
*需要特别说明的是rgisterCallback方法,它调用上面定义的函数原型方法bind,并传递自己为参数。
*之所以这样做,是因为setTimeout默认总以window对象为当前对象,也就是说,如果registerCallback方法定义如下的话:
*registerCallback:function(){
*setTimeout(this.onTimerEvent,this.frequency*1000);
*}
*那么,this.onTimeoutEvent方法执行失败,因为它无法访问this.currentlyExecuting属性。
*而使用了bind以后,该方法才能正确的找到this,也就是PeriodicalExecuter的当前实例。
*/
varPeriodicalExecuter=Class.create();
PeriodicalExecuter.prototype={
initialize:function(callback,frequency){
this.callback=callback;
this.frequency=frequency;
this.currentlyExecuting=false;
this.registerCallback();
},
registerCallback:function(){
setTimeout(this.onTimerEvent.bind(this),this.frequency*1000);
},
onTimerEvent:function(){
if(!this.currentlyExecuting){
try{
this.currentlyExecuting=true;
this.callback();
}finally{
this.currentlyExecuting=false;
}
}
this.registerCallback();
}
}
/*--------------------------------------------------------------------------*/
/**
*这个函数就Ruby了。我觉得它的作用主要有两个
*1.大概是document.getElementById(id)的最简化调用。
*比如:$(aaa)将返回上aaa对象
*2.得到对象数组
*比如:$(aaa,bbb)返回一个包括id为aaa和bbb两个input控件对象的数组。
*/
function$(){
varelements=newArray();
for(vari=0;i<arguments.length;i++){
varelement=arguments[i];
if(typeofelement==string)
element=document.getElementById(element);
if(arguments.length==1)
returnelement;
elements.push(element);
}
returnelements;
}
/**
*定义Ajax对象,静态方法getTransport方法返回一个XMLHttp对象
*/
varAjax={
getTransport:function(){
returnTry.these(
function(){returnnewActiveXObject(Msxml2.XMLHTTP)},
function(){returnnewActiveXObject(Microsoft.XMLHTTP)},
function(){returnnewXMLHttpRequest()}
)false;
},
emptyFunction:function(){}
}
/**
*我以为此时的Ajax对象起到命名空间的作用。
*Ajax.Base声明为一个基础对象类型
*注意Ajax.Base并没有使用Class.create()的方式来创建,我想是因为作者并不希望Ajax.Base被库使用者实例化。
*作者在其他对象类型的声明中,将会继承于它。
*就好像java中的私有抽象类
*/
Ajax.Base=function(){};
Ajax.Base.prototype={
/**
*extend(见prototype.js中的定义)的用法真是让人耳目一新
*options首先设置默认属性,然后再extend参数对象,那么参数对象中也有同名的属性,那么就覆盖默认属性值。
*想想如果我写这样的实现,应该类似如下:
setOptions:function(options){
..........
}
我想很多时候,java限制了js的创意。
*/
setOptions:function(options){
this.options={
method:post,
asynchronous:true,
parameters:
}.extend(options{});
}
}
/**
*Ajax.Request封装XmlHttp
*/
Ajax.Request=Class.create();
/**
*定义四种事件(状态),参考
*/
Ajax.Request.Events=
[Uninitialized,Loading,Loaded,Interactive,Complete];
/**
*
*/
Ajax.Request.prototype=(newAjax.Base()).extend({
initialize:function(url,options){
this.transport=Ajax.getTransport();
this.setOptions(options);
try{
if(this.options.method==get)
url+=?+this.options.parameters+&_=;
/**
*此处好像强制使用了异步方式,而不是依照this.options.asynchronous的值
*/
this.transport.open(this.options.method,url,true);
/**
*这里提供了XmlHttp传输过程中每个步骤的回调函数
*/
if(this.options.asynchronous){
this.transport.onreadystatechange=this.onStateChange.bind(this);
setTimeout((function(){this.respondToReadyState(1)}).bind(this),10);
}
this.transport.setRequestHeader(X-Requested-With,XMLHttpRequest);
this.transport.setRequestHeader(X-Prototype-Version,Prototype.Version);
if(this.options.method==post){
this.transport.setRequestHeader(Connection,close);
this.transport.setRequestHeader(Content-type,
application/x-
}
this.transport.send(this.options.method==post?
this.options.parameters+&_=:null);
}catch(e){
}
},
onStateChange:function(){
varreadyState=this.transport.readyState;
/**
*如果不是Loading状态,就调用回调函数
*/
if(readyState!=1)
this.respondToReadyState(this.transport.readyState);
},
/**
*回调函数定义在this.options属性中,比如:
varoption={
onLoaded:function(req){...};
......
}
newAjax.Request(url,option);
*/
respondToReadyState:function(readyState){
varevent=Ajax.Request.Events[readyState];
(this.options[on+event]Ajax.emptyFunction)(this.transport);
}
});
/**
*Ajax.Updater用于绑定一个html元素与XmlHttp调用的返回值。类似与buffalo的bind。
*如果options中有insertion(fromdom.js)对象的话,insertion能提供更多的插入控制。
*/
Ajax.Updater=Class.create();
Ajax.Updater.prototype=(newAjax.Base()).extend({
initialize:function(container,url,options){
this.setOptions(options);
if(this.options.asynchronous){
this.onComplete=this.options.onComplete;
this.options.onComplete=this.updateContent.bind(this);
}
this.request=newAjax.Request(url,this.options);
if(!this.options.asynchronous)
this.updateContent();
},
updateContent:function(){
if(this.options.insertion){
newthis.options.insertion(this.container,
this.request.transport.responseText);
}else{
this.container.innerHTML=this.request.transport.responseText;
}
if(this.onComplete){
setTimeout((function(){this.onComplete(this.request)}).bind(this),10);
}
}
});
/**
*针对页面元素对象的工具类,提供一些简单静态方法
*/
varField={
/**
*清除参数引用对象的值
*/
clear:function(){
for(vari=0;i<arguments.length;i++)
$(arguments[i]).value=;
},
/**
*使参数引用对象获取焦点
*/
focus:function(element){
$(element).focus();
},
/**
*判断参数引用对象值是否为空,如为空,返回false,反之true
*/
present:function(){
for(vari=0;i<arguments.length;i++)
if($(arguments[i]).value==)returnfalse;
returntrue;
},
/**
*使选中参数引用对象
*/
select:function(element){
$(element).select();
},
/**
*使参数引用对象处于可编辑状态
*/
activate:function(element){
$(element).focus();
$(element).select();
}
}
/*--------------------------------------------------------------------------*/
/**
*表单工具类
*/
varForm={
/**
*将表单元素序列化后的值组合成QueryString的形式
*/
serialize:function(form){
varelements=Form.getElements($(form));
varqueryComponents=newArray();
for(vari=0;i<elements.length;i++){
varqueryComponent=Form.Element.serialize(elements[i]);
if(queryComponent)
queryComponents.push(queryComponent);
}
returnqueryComponents.join(&);
},
/**
*得到表单的所有元素对象
*/
getElements:function(form){
form=$(form);
varelements=newArray();
for(tagNameinForm.Element.Serializers){
vartagElements=form.getElementsByTagName(tagName);
for(varj=0;j<tagElements.length;j++)
elements.push(tagElements[j]);
}
returnelements;
},
/**
*将指定表单的元素置于不可用状态
*/
disable:function(form){
varelements=Form.getElements(form);
for(vari=0;i<elements.length;i++){
varelement=elements[i];
element.blur();
element.disable=true;
}
},
/**
*使表单的第一个非hidden类型而且处于可用状态的元素获得焦点
*/
focusFirstElement:function(form){
form=$(form);
varelements=Form.getElements(form);
for(vari=0;i<elements.length;i++){
varelement=elements[i];
if(element.type!=hidden&&!element.disabled){
Field.activate(element);
break;
}
}
},
/*
*重置表单
*/
reset:function(form){
$(form).reset();
}
}
/**
*表单元素工具类
*/
Form.Element={
/**
*返回表单元素的值先序列化再进行URL编码后的值
*/
serialize:function(element){
element=$(element);
varmethod=element.tagName.toLowerCase();
varparameter=Form.Element.Serializers[method](element);
if(parameter)
returnencodeURIComponent(parameter[0])+=+
encodeURIComponent(parameter[1]);
},
/**
*返回表单元素序列化后的值
*/
getValue:function(element){
element=$(element);
varmethod=element.tagName.toLowerCase();
varparameter=Form.Element.Serializers[method](element);
if(parameter)
returnparameter[1];
}
}
/**
*prototype的所谓序列化其实就是将表单的名字和值组合成一个数组
*/
Form.Element.Serializers={
input:function(element){
switch(element.type.toLowerCase()){
casehidden:
casepassword:
casetext:
returnForm.Element.Serializers.textarea(element);
casecheckbox:
caseradio:
returnForm.Element.Serializers.inputSelector(element);
}
returnfalse;
},
inputSelector:function(element){
if(element.checked)
},
textarea:function(element){
return[element.name,element.value];
},
/**
*看样子,也不支持多选框(select-multiple)
*/
select:function(element){
varindex=element.selectedIndex;
varvalue=element.options[index].valueelement.options[index].text;
return[element.name,(index>=0)?value:];
}
}
/*--------------------------------------------------------------------------*/
/**
*Form.Element.getValue也许会经常用到,所以做了一个快捷引用
*/
var$F=Form.Element.getValue;
/*--------------------------------------------------------------------------*/
/**
*Abstract.TimedObserver也没有用Class.create()来创建,和Ajax.Base意图应该一样
*Abstract.TimedObserver顾名思义,是套用Observer设计模式来跟踪指定表单元素,
*当表单元素的值发生变化的时候,就执行回调函数
*
*我想Observer与注册onchange事件相似,不同点在于onchange事件是在元素失去焦点的时候才激发。
*同样的与onpropertychange事件也相似,不过它只关注表单元素的值的变化,而且提供timeout的控制。
*
*除此之外,Observer的好处大概就在与更面向对象,另外可以动态的更换回调函数,这就比注册事件要灵活一些。
*Observer应该可以胜任动态数据校验,或者多个关联下拉选项列表的连动等等
*
*/
Abstract.TimedObserver=function(){}
/**
*这个设计和PeriodicalExecuter一样,bind方法是实现的核心
*/
Abstract.TimedObserver.prototype={
initialize:function(element,frequency,callback){
this.frequency=frequency;
this.element=$(element);
this.callback=callback;
this.lastValue=this.getValue();
this.registerCallback();
},
registerCallback:function(){
setTimeout(this.onTimerEvent.bind(this),this.frequency*1000);
},
onTimerEvent:function(){
varvalue=this.getValue();
if(this.lastValue!=value){
this.callback(this.element,value);
this.lastValue=value;
}
this.registerCallback();
}
}
/**
*Form.Element.Observer和Form.Observer其实是一样的
*注意Form.Observer并不是用来跟踪整个表单的,我想大概只是为了减少书写(这是Ruby的一个设计原则)
*/
Form.Element.Observer=Class.create();
Form.Element.Observer.prototype=(newAbstract.TimedObserver()).extend({
getValue:function(){
returnForm.Element.getValue(this.element);
}
});
Form.Observer=Class.create();
Form.Observer.prototype=(newAbstract.TimedObserver()).extend({
getValue:function(){
returnForm.serialize(this.element);
}
});
/**
*根据classattribute的名字得到对象数组,支持multipleclass
*
*/
document.getElementsByClassName=function(className){
varchildren=document.getElementsByTagName(*)document.all;
varelements=newArray();
for(vari=0;i<children.length;i++){
varchild=children[i];
varclassNames=child.className.split();
for(varj=0;j<classNames.length;j++){
if(classNames[j]==className){
elements.push(child);
break;
}
}
}
returnelements;
}
/*--------------------------------------------------------------------------*/
/**
*Element就象一个java的工具类,主要用来隐藏/显示/销除对象,以及获取对象的简单属性。
*
*/
varElement={
toggle:function(){
for(vari=0;i<arguments.length;i++){
varelement=$(arguments[i]);
element.style.display=
(element.style.display==none?:none);
}
},
hide:function(){
for(vari=0;i<arguments.length;i++){
varelement=$(arguments[i]);
element.style.display=none;
}
},
show:function(){
for(vari=0;i<arguments.length;i++){
varelement=$(arguments[i]);
element.style.display=;
}
},
remove:function(element){
element=$(element);
element.parentNode.removeChild(element);
},
getHeight:function(element){
element=$(element);
returnelement.offsetHeight;
}
}
/**
*为Element.toggle做了一个符号连接,大概是兼容性的考虑
*/
varToggle=newObject();
Toggle.display=Element.toggle;
/*--------------------------------------------------------------------------*/
/**
*动态插入内容的实现,MS的Jscript实现中对象有一个insertAdjacentHTML方法(
*这里算是一个对象形式的封装。
*/
Abstract.Insertion=function(adjacency){
this.adjacency=adjacency;
}
Abstract.Insertion.prototype={
initialize:function(element,content){
this.element=$(element);
this.content=content;
if(this.adjacency&&this.element.insertAdjacentHTML){
this.element.insertAdjacentHTML(this.adjacency,this.content);
}else{
/**
*gecko不支持insertAdjacentHTML方法,但可以用如下代码代替
*/
this.range=this.element.ownerDocument.createRange();
/**
*如果定义了initializeRange方法,则实行,这里相当与定义了一个抽象的initializeRange方法
*/
if(this.initializeRange)this.initializeRange();
this.fragment=this.range.createContextualFragment(this.content);
/**
*insertContent也是一个抽象方法,子类必须实现
*/
this.insertContent();
}
}
}
/**
*prototype加深了我的体会,就是写js如何去遵循DontRepeatYourself(DRY)原则
*上文中Abstract.Insertion算是一个抽象类,定义了名为initializeRange的一个抽象方法
*varInsertion=newObject()建立一个命名空间
*Insertion.BeforeTopBottomAfter就象是四个java中的四个静态内部类,而它们分别继承于Abstract.Insertion,并实现了initializeRange方法。
*/
varInsertion=newObject();
Insertion.Before=Class.create();
Insertion.Before.prototype=(newAbstract.Insertion(beforeBegin)).extend({
initializeRange:function(){
this.range.setStartBefore(this.element);
},
/**
*将内容插入到指定节点的前面,与指定节点同级
*/
insertContent:function(){
this.element.parentNode.insertBefore(this.fragment,this.element);
}
});
Insertion.Top.prototype=(newAbstract.Insertion(afterBegin)).extend({
initializeRange:function(){
this.range.selectNodeContents(this.element);
this.range.collapse(true);
},
/**
*将内容插入到指定节点的第一个子节点前,于是内容变为该节点的第一个子节点
*/
insertContent:function(){
this.element.insertBefore(this.fragment,this.element.firstChild);
}
});
Insertion.Bottom=Class.create();
Insertion.Bottom.prototype=(newAbstract.Insertion(beforeEnd)).extend({
initializeRange:function(){
this.range.selectNodeContents(this.element);
this.range.collapse(this.element);
},
/**
*将内容插入到指定节点的最后,于是内容变为该节点的最后一个子节点
*/
insertContent:function(){
this.element.appendChild(this.fragment);
}
});
Insertion.After=Class.create();
Insertion.After.prototype=(newAbstract.Insertion(afterEnd)).extend({
initializeRange:function(){
this.range.setStartAfter(this.element);
},
/**
*将内容插入到指定节点的后面,与指定节点同级
*/
insertContent:function(){
this.element.parentNode.insertBefore(this.fragment,
this.element.nextSibling);
}
});
/*这是包含错误的原版本
if(!Function.prototype.apply){
//Basedoncodefrom
Function.prototype.apply=function(object,parameters){
varparameterStrings=newArray();
if(!object)object=window;
if(!parameters)parameters=newArray();
for(vari=0;i<parameters.length;i++)
parameterStrings[i]=x[+i+];//Error1
object.__apply__=this;
varresult=eval(obj.__apply__(+//Error2
parameterStrings[i].join(,)+));
object.__apply__=null;
returnresult;
}
}
*/
if(!Function.prototype.apply){
Function.prototype.apply=function(object,parameters){
varparameterStrings=newArray();
if(!object)object=window;
if(!parameters)parameters=newArray();
for(vari=0;i<parameters.length;i++)
parameterStrings[i]=parameters[+i+];
object.__apply__=this;
varresult=eval(object.__apply__(+parameterStrings.join(,)+));
object.__apply__=null;
returnresult;
}
}
Effect的一个子类
Effect.Blink=Class.create();
Effect.Blink.prototype={
initialize:function(element,frequency){
this.element=$(element);
this.frequency=frequency?frequency:1000;
this.element.effect_blink=this;
this.blink();
},
blink:function(){
if(this.timer)clearTimeout(this.timer);
try{
this.element.style.visibility=this.element.style.visibility==hidden?visible:hidden;
}catch(e){}
this.timer=setTimeout(this.blink.bind(this),this.frequency);
}
};
文章标题:prototype 源码中文说明之 prototype.js
文章链接:http://soscw.com/essay/18075.html