prototype源代码解读(转载自javaeye) - gooforward - CSD...

来源:百度文库 编辑:神马文学网 时间:2024/04/30 22:49:02
 
prototype源代码解读(转载自javaeye)
 
/*** 定义一个全局对象, 属性 Version 在发布的时候会替换为当前版本号*/var Prototype = {Version: '1.3.1',// 一个空方法,其后的代码常会用到,先前的版本该方法被定义于 Ajax 类中。  emptyFunction: function() {}}/*** 创建一种类型,注意其属性 create 是一个方法,返回一个构造函数。* 一般使用如下*     var X = Class.create();  返回一个类型,类似于 java 的一个Class实例。* 要使用 X 类型,需继续用 new X()来获取一个实例,如同 java 的 Class.newInstance()方法。** 返回的构造函数会执行名为 initialize 的方法, initialize 是 Ruby 对象的构造器方法名字。* 此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法。** 如果一定要从java上去理解。你可以理解为用Class.create()创建一个继承java.lang.Class类的类。* 当然java不允许这样做,因为Class类是final的**/var Class = {create: function() {return function() {this.initialize.apply(this, arguments);}}}/*** 创建一个对象,从变量名来思考,本意也许是定义一个抽象类,以后创建新对象都 extend 它。* 但从其后代码的应用来看, Abstract 更多是为了保持命名空间清晰的考虑。* 也就是说,我们可以给 Abstract 这个对象实例添加新的对象定义。** 从java去理解,就是动态给一个对象创建内部类。*/var Abstract = new Object();Object.extend = function(destination, source) {for (property in source) {destination[property] = source[property];}return destination;}/*** 获取参数对象的所有属性和方法,有点象多重继承。但是这种继承是动态获得的。* 如:*     var a = new ObjectA(), b = new ObjectB();*     var c = a.extend(b);* 此时 c 对象同时拥有 a 和 b 对象的属性和方法。但是与多重继承不同的是,c instanceof ObjectB 将返回false。** 旧版本的该方法定义如下:* Object.prototype.extend = function(object) {*     for (property in object) {*         this[property] = object[property];*     }*     return this;* }** 新的形式新定义了一个静态方法 Object.extend,这样做的目的大概是为了使代码更为清晰*/Object.prototype.extend = function(object) {return Object.extend.apply(this, [this, object]);}/*** 这个方法很有趣,它封装一个javascript函数对象,返回一个新函数对象,新函数对象的主体和原对象相同,* 但是bind()方法参数将被用作当前对象的对象。* 也就是说新函数中的 this 引用被改变为参数提供的对象。* 比如:*     *     *     .................*     * 那么,调用aaa.showValue 将返回"aaa", 但调用aaa.showValue2 将返回"bbb"。** apply 是ie5.5后才出现的新方法(Netscape好像很早就支持了)。* 该方法更多的资料参考MSDN http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthApply.asp * 阅读其后的代码就会发现,bind 被应用的很广泛,该方法和 Object.prototype.extend 一样是 Prototype 的核心。* 还有一个 call 方法,应用起来和 apply 类似。可以一起研究下。*/Function.prototype.bind = function(object) {var __method = this;return function() {__method.apply(object, arguments);}}/*** 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象* 注意这时候,用到了 Function.call。它与 Function.apply 的不同好像仅仅是对参数形式的定义。* 如同 java 两个过载的方法。*/Function.prototype.bindAsEventListener = function(object) {var __method = this;return function(event) {__method.call(object, event || window.event);}}/*** 将整数形式RGB颜色值转换为HEX形式*/Number.prototype.toColorPart = function() {var digits = this.toString(16);if (this < 16) return '0' + digits;return digits;}/*** 典型 Ruby 风格的函数,将参数中的方法逐个调用,返回第一个成功执行的方法的返回值*/var Try = {these: function() {var returnValue;for (var i = 0; i < arguments.length; i++) {var lambda = arguments[i];try {returnValue = lambda();break;} catch (e) {}}return returnValue;}}/*--------------------------------------------------------------------------*//*** 一个设计精巧的定时执行器* 首先由 Class.create() 创建一个 PeriodicalExecuter 类型,* 然后用对象直接量的语法形式设置原型。** 需要特别说明的是 rgisterCallback 方法,它调用上面定义的函数原型方法bind, 并传递自己为参数。* 之所以这样做,是因为 setTimeout 默认总以 window 对象为当前对象,也就是说,如果 registerCallback 方法定义如下的话:*     registerCallback: function() {*         setTimeout(this.onTimerEvent, this.frequency * 1000);*     }* 那么,this.onTimeoutEvent 方法执行失败,因为它无法访问 this.currentlyExecuting 属性。* 而使用了bind以后,该方法才能正确的找到this,也就是PeriodicalExecuter的当前实例。*/var PeriodicalExecuter = Class.create();PeriodicalExecuter.prototype = {initialize: function(callback, frequency) {this.callback = callback;this.frequency = frequency;this.currentlyExecuting = false;this.registerCallback();},registerCallback: function() {setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);},onTimerEvent: function() {if (!this.currentlyExecuting) {try {this.currentlyExecuting = true;this.callback();} finally {this.currentlyExecuting = false;}}}}/*--------------------------------------------------------------------------*//*** 这个函数就 Ruby 了。我觉得它的作用主要有两个* 1.  大概是 document.getElementById(id) 的最简化调用。* 比如:$("aaa") 将返回 aaa 对象* 2.  得到对象数组* 比如: $("aaa","bbb") 返回一个包括id为"aaa"和"bbb"两个input控件对象的数组。*/function $() {var elements = new Array();for (var i = 0; i < arguments.length; i++) {var element = arguments[i];if (typeof element == 'string')element = document.getElementById(element);if (arguments.length == 1)return element;elements.push(element);}return elements;}/*** 为兼容旧版本的浏览器增加 Array 的 push 方法。*/if (!Array.prototype.push) {Array.prototype.push = function() {var startLength = this.length;for (var i = 0; i < arguments.length; i++)this[startLength + i] = arguments[i];return this.length;}}/*** 为兼容旧版本的浏览器增加 Function 的 apply 方法。*/if (!Function.prototype.apply) {// Based on code from http://www.youngpup.net/  Function.prototype.apply = function(object, parameters) {var parameterStrings = new Array();if (!object)     object = window;if (!parameters) parameters = new Array();for (var i = 0; i < parameters.length; i++)parameterStrings[i] = 'parameters[' + i + ']';object.__apply__ = this;var result = eval('object.__apply__(' +parameterStrings.join(', ') + ')');object.__apply__ = null;return result;}}/*** 扩展 javascript 内置的 String 对象*/String.prototype.extend({/*** 去掉字符串中的标签*/stripTags: function() {return this.replace(/<\/?[^>]+>/gi, '');},/*** 这个方法很常见,通常的实现都是用正则表达式替换特殊字符为html规范定义的命名实体或者十进制编码,比如:* string.replace(/&/g, "&").replace(//g, ">");* 而这里的实现借用浏览器自身的内部替换,确实巧妙。*/escapeHTML: function() {var div = document.createElement('div');var text = document.createTextNode(this);div.appendChild(text);return div.innerHTML;},/*** 同上*/unescapeHTML: function() {var div = document.createElement('div');div.innerHTML = this.stripTags();return div.childNodes[0].nodeValue;}});/*** 定义 Ajax 对象, 静态方法 getTransport 方法返回一个 XMLHttp 对象*/var Ajax = {getTransport: function() {return Try.these(function() {return new ActiveXObject('Msxml2.XMLHTTP')},function() {return new ActiveXObject('Microsoft.XMLHTTP')},function() {return new XMLHttpRequest()}) || false;}}/*** 我以为此时的Ajax对象起到命名空间的作用。* Ajax.Base 声明为一个基础对象类型* 注意 Ajax.Base 并没有使用 Class.create() 的方式来创建,我想是因为作者并不希望 Ajax.Base 被库使用者实例化。* 作者在其他对象类型的声明中,将会继承于它。* 就好像 java 中的私有抽象类*/Ajax.Base = function() {};Ajax.Base.prototype = {/*** extend (见上) 的用法真是让人耳目一新* options 首先设置默认属性,然后再 extend 参数对象,那么参数对象中也有同名的属性,那么就覆盖默认属性值。* 想想如果我写这样的实现,应该类似如下:setOptions: function(options) {this.options.methed = options.methed? options.methed : 'post';..........}我想很多时候,java 限制了 js 的创意。*/setOptions: function(options) {this.options = {method:       'post',asynchronous: true,parameters:   ''}.extend(options || {});},/*** 如果 xmlhttp 调用返回正确的HTTP状态值,函数返回ture, 反之false。* xmlhttp 的 readyState 属性不足以准确判断 xmlhttp 远程调用成功,该方法是readyState判断的一个前提条件*/responseIsSuccess: function() {return this.transport.status == undefined|| this.transport.status == 0|| (this.transport.status >= 200 && this.transport.status < 300);},/*** 如果 xmlhttp 调用返回错误的HTTP状态值,函数返回ture, 反之false。*/responseIsFailure: function() {return !this.responseIsSuccess();}}/*** Ajax.Request 封装 XmlHttp*/Ajax.Request = Class.create();/*** 定义四种事件(状态), 参考http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/readystate_1.asp */Ajax.Request.Events =['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];/*** 相比先前的版本,对于 xmlhttp 的调用和返回值处理分离得更为清晰*/Ajax.Request.prototype = (new Ajax.Base()).extend({initialize: function(url, options) {this.transport = Ajax.getTransport();this.setOptions(options);this.request(url);}, /*** 新增加 request 方法封装 xmlhttp 的调用过程。*/request: function(url) {var parameters = this.options.parameters || '';if (parameters.length > 0) parameters += '&_=';try {if (this.options.method == 'get')url += '?' + parameters;this.transport.open(this.options.method, url,this.options.asynchronous);if (this.options.asynchronous) {this.transport.onreadystatechange = this.onStateChange.bind(this);setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);}this.setRequestHeaders();var body = this.options.postBody ? this.options.postBody : parameters;this.transport.send(this.options.method == 'post' ? body : null);} catch (e) {}},/*** 新增加的 setRequestHeaders 方法允许添加自定义的http header*/setRequestHeaders: function() {var requestHeaders =['X-Requested-With', 'XMLHttpRequest','X-Prototype-Version', Prototype.Version];if (this.options.method == 'post') {requestHeaders.push('Content-type','application/x-www-form-urlencoded');/* Force "Connection: close" for Mozilla browsers to work around* a bug where XMLHttpReqeuest sends an incorrect Content-length* header. See Mozilla Bugzilla #246651.*/if (this.transport.overrideMimeType)requestHeaders.push('Connection', 'close');}/*** 其后的 apply 方法的调用有些奇技淫巧的意味* 从上下文中我们可以分析出 this.options.requestHeaders 是调用者自定义的http header数组。* requestHeaders 也是一个数组,将一个数组中的元素逐个添加到另一个元素中,直接调用* requestHeaders.push(this.options.requestHeaders)* 是不行的,因为该调用导致 this.options.requestHeaders 整个数组作为一个元素添加到 requestHeaders中。* javascript的Array对象还提供一个concat 的方法表面上满足要求,但是concat实际上是创建一个新数组,将两个数组的元素添加到新数组中。* 所以,下面的代码也可以替换为* requestHeaders = requestHeaders.concat(this.options.requestHeaders);* 很显然,作者不喜欢这样的代码方式* 而 apply 方法的语法 apply([thisObj[,argArray]]) 本身就要求第二个参数是一个数组或者arguments对象。* 所以巧妙的实现了 concat 函数的作用。* 令人拍案叫绝啊!*/if (this.options.requestHeaders)requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);for (var i = 0; i < requestHeaders.length; i += 2)this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);},onStateChange: function() {var readyState = this.transport.readyState;/*** 如果不是 Loading 状态,就调用回调函数*/if (readyState != 1)this.respondToReadyState(this.transport.readyState);},/*** 回调函数定义在 this.options 属性中,比如:var option = {onLoaded : function(req) {...};......}new Ajax.Request(url, option);*/respondToReadyState: function(readyState) {var event = Ajax.Request.Events[readyState];/*** 新增的回调函数处理,调用者还可以在options中定义 on200, onSuccess 这样的回调函数* 在 readyState 为完成状态的时候调用*/if (event == 'Complete')(this.options['on' + this.transport.status]|| this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]|| Prototype.emptyFunction)(this.transport);(this.options['on' + event] || Prototype.emptyFunction)(this.transport);/* Avoid memory leak in MSIE: clean up the oncomplete event handler */if (event == 'Complete')this.transport.onreadystatechange = Prototype.emptyFunction;}});/*** Ajax.Updater 用于绑定一个html元素与 XmlHttp调用的返回值。类似与 buffalo 的 bind。* 如果 options 中有 insertion(见后) 对象的话, insertion 能提供更多的插入控制。*/Ajax.Updater = Class.create();Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)';Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({initialize: function(container, url, options) {/*** containers 就是被绑定的 html 对象,xmlhttp的返回值被赋给该对象的 innerHTML 属性。* 相比新版本,containers 根据container参数定义 success 和 failure 引用,如果它们被定义的话,根据xmlhttp调用是否成功来选择* 更新对象,假想调用可能如下:* var c = {success: $("successDiv"), failure: $("failureDiv")};* new Ajax.Updater(c, url, options);* 那么调用成功则 successDiv 显示成功信息或者数据,反之 failureDiv 显示错误信息*/this.containers = {success: container.success ? $(container.success) : $(container),failure: container.failure ? $(container.failure) :(container.success ? null : $(container))}this.transport = Ajax.getTransport();this.setOptions(options);var onComplete = this.options.onComplete || Prototype.emptyFunction;this.options.onComplete = (function() {this.updateContent();onComplete(this.transport);}).bind(this);this.request(url);},updateContent: function() {var receiver = this.responseIsSuccess() ?this.containers.success : this.containers.failure;var match    = new RegExp(Ajax.Updater.ScriptFragment, 'img');var response = this.transport.responseText.replace(match, '');var scripts  = this.transport.responseText.match(match);if (receiver) {if (this.options.insertion) {new this.options.insertion(receiver, response);} else {receiver.innerHTML = response;}}if (this.responseIsSuccess()) {if (this.onComplete)setTimeout((function() {this.onComplete(this.transport)}).bind(this), 10);}/*** 如果调用者在传入的options参数中定义 evalScripts=true,同时xmlhttp返回值的html中包含 文本。* 没有g, scripts[i].match(match)[1] 匹配的就是