var Ajax = { getTransport: function() { /*之前定义的Try.these终于派上用场了*/ return Try.these( function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, /*用来统计活动的连接数*/ activeRequestCount: 0 } /*Ajax.Responders同样mixin了Enumerable对象*/ Ajax.Responders = { responders: [], _each: function(iterator) { this.responders._each(iterator); }, /*注册一个responder,重复注册没有任何副作用*/ /*参数为responder的名称*/ register: function(responder) { if (!this.include(responder)) this.responders.push(responder); }, /*撤销注册*/ /*参数为responder的名称*/ unregister: function(responder) { this.responders = this.responders.without(responder); }, /*遍历所有注册的responder,并在调用其上的用callback指示的函数,这些函数接受一个数组作为参数 数组包含request,transport,json三项*/ dispatch: function(callback, request, transport, json) { this.each(function(responder) { if (typeof responder[callback] == 'function') { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) {} } }); } }; Object.extend(Ajax.Responders, Enumerable); /*定义两个静态方法 onCreate和onComplete */ Ajax.Responders.register({ onCreate: function() { /*做的工作很简单,增加一个活动链接数*/ Ajax.activeRequestCount++; }, onComplete: function() { Ajax.activeRequestCount--; } }); Ajax.Base = function() {}; Ajax.Base.prototype = { /*Ajax.Base的每个实例都有一个setOptions方法,此方法接收一个哈希对象作为参数,或者不传入参数,使用默认值*/ /*哈希对象的格式为{param1:value1,param2:value2...}*/ /*parameters为一个字符串,形为param1=value1¶m2=value2....¶mn=valuen#dock*/ /*经过处理后parameters会被拆分然后填充到一个对象中去,对象的格式为 {param1=value1;param2=value2....paramn=valuen} */ /*默认值为 method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '' 可能含有的其他参数 postBody requestHeaders evalScripts(Ajax.Updater) insertion(Ajax.Updater) frequency(Ajax.PeriodicalUpdater) decay(Ajax.PeriodicalUpdater) 可能含有的方法,用户只需要定义自己关心事件的回调函数。 onSuccess onFailure on2xx on3xx on4xx on5xx onUninitialized onLoading onLoaded onInteractive onComplete */ /*usage ajaxBaseInstance.setOptions( {postBody:"...",onSuccess:function(){...},onComplete:function(){...}} ); */ setOptions: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '' } Object.extend(this.options, options || {}); this.options.method = this.options.method.toLowerCase(); if (typeof this.options.parameters == 'string') this.options.parameters = this.options.parameters.toQueryParams(); } } Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { _complete: false, initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); /*新建一个AjaxRequest对象的时候直接发起请求*/ this.request(url); }, /* (如果state等于Complete) (如果response是js代码) /*onCreate---->onLoading------> on2xx -------------->调用evalResponse-----------> onUninitialized------ on3xx onLoading | on4xx onLoaded | on5xx onInteractive | (transport状态更改会导致循环这个过程) onSuccess onComplete | onFailure | |___________________________________________________________________| 注:所有这些函数均接受两个参数transport, json */ request: function(url) { this.url = url; this.method = this.options.method; var params = Object.clone(this.options.parameters); /*如果this.method既不是get也不是post(在setOptions的时候被重写了,可能是put或者delete之类的) 则把this.method的值保存在params._method中,并把this.method设置为post(因为不论put还是delete, 其实都要向服务器端提交数据,在现有绝大部分浏览器仅支持get和post的情况下可以用post来模拟)*/ if (!['get', 'post'].include(this.method)) { // simulate other verbs over post params['_method'] = this.method; this.method = 'post'; } /*将params对象保存到this.parameters对象中,params将会保存toQueryString后的结果*/ this.parameters = params; if (params = Hash.toQueryString(params)) { // when GET, append parameters to URL /*这里多说一句,万一我传入的url包含锚点符号怎么办?。。。*/ if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='; } try { if (this.options.onCreate) this.options.onCreate(this.transport); /*在创建Ajax调用之前*/ /*对所有注册的Responders调用它们的onCreate方法*/ Ajax.Responders.dispatch('onCreate', this, this.transport); /*发起一个Ajax调用,默认使用异步方法*/ /*如果是同步的话会一直在这里等到连接建立或者超时*/ this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); /*如果是使用异步调用,等待10毫秒后将会调用respondToReadyState方法,参数为1表示Loading状态*/ /*人为的触发一个1xx事件(这个时候还没有使用onStateChange回调),以便于做一些等待时的处理,譬如显示一个Loading提示,或者进度条什么的*/ if (this.options.asynchronous) setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); /*设置transport.onreadystatechange事件的回调函数为Ajax.Request.prototype.onStateChange*/ /*无论是同步还是异步,这个onStatechange事件都会被调用*/ this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); this.body = this.method == 'post' ? (this.options.postBody || params) : null; /*发送请求*/ this.transport.send(this.body); /* Force Firefox to handle ready state 4 for synchronous requests */ if (!this.options.asynchronous && this.transport.overrideMimeType) this.onStateChange(); } catch (e) { this.dispatchException(e); } }, /* 1xx:信息响应类,表示接收到请求并且继续处理 2xx:处理成功响应类,表示动作被成功接收、理解和接受 3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理 4xx:客户端错误,客户请求包含语法错误或者是不能正确执行 5xx:服务端错误,服务器不能正确执行一个正确的请求 */ onStateChange: function() { var readyState = this.transport.readyState; if (readyState > 1 && !((readyState == 4) && this._complete/*在一个成功的调用之前,这个值一直都被设置为false*/)) this.respondToReadyState(this.transport.readyState); }, /*核心是调用transport.setRequestHeader方法*/ setRequestHeaders: function() { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': Prototype.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }; if (this.method == 'post') { headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : ''); /* Force "Connection: close" for older Mozilla browsers to work * around a bug where XMLHttpRequest sends an incorrect * Content-length header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) headers['Connection'] = 'close'; } // user-defined headers if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; if (typeof extras.push == 'function') for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else $H(extras).each(function(pair) { headers[pair.key] = pair.value }); } for (var name in headers) this.transport.setRequestHeader(name, headers[name]); }, /*transport.status为false,null,undefined,NaN也表示success?*/ /*或者transport.status为2xx*/ success: function() { return !this.transport.status || (this.transport.status >= 200 && this.transport.status < 300); }, respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState]; var transport = this.transport, json = this.evalJSON(); /*查看this.options里面是否定义了onXXX(2xx~5xx)或者onSuccess或者onFailure方法*/ /*如果定义了就调用相应的方法,如果没有定义则调用空方法*/ if (state == 'Complete') { try { /*设置this._complete参数为true*/ this._complete = true; (this.options['on' + this.transport.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(transport, json); } catch (e) { this.dispatchException(e); } /*通过比较响应的Content-type来查看是否是javascript代码,如果是,就动态eval返回的js代码*/ var contentType = this.getHeader('Content-type'); if (contentType && contentType.strip(). match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) this.evalResponse(); } /*根据返回结果不同执行不同的回调函数*/ /* onUninitialized onLoading onLoaded onInteractive onComplete */ /*从代码可以看出来如果transport的状态码为4,这里会先后执行两个回调函数, 首先是onXXX(2xx~5xx)或者onSuccess或者onFailure方法 接着是上述函数之一*/ try { (this.options['on' + state] || Prototype.emptyFunction)(transport, json); Ajax.Responders.dispatch('on' + state, this, transport, json); } catch (e) { this.dispatchException(e); } if (state == 'Complete') { // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; } }, getHeader: function(name) { try { return this.transport.getResponseHeader(name); } catch (e) { return null } }, evalJSON: function() { try { var json = this.getHeader('X-JSON'); return json ? json.evalJSON() : null; } catch (e) { return null } }, evalResponse: function() { try { return eval((this.transport.responseText || '').unfilterJSON()); } catch (e) { this.dispatchException(e); } }, dispatchException: function(exception) { /*首先进行统一的异常处理(通过定义this.options.onException方法 然后分别执行每个注册了的Responder的onException方法*/ (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); Ajax.Updater = Class.create(); /*这里有一个小技巧是先mixin,然后复写其中需要的方法*/ Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize: function(container, url, options) { /*这里用来指定Ajax调用成功和失败后分别用来更新的DOM对象 参数可以是一个代表指定DOM元素的字符串(无论成功失败均更新此DOM对象),也可以是一个包含success和failure属性的对象*/ this.container = { success: (container.success || container), failure: (container.failure || (container.success ? null : container)) } this.transport = Ajax.getTransport(); this.setOptions(options); /*在用户定义的onComplete事件前插入一个updateContent()事件*/ /*这个updateContent事件是已经定义好的,所以无论用户是否定义onComplete, 这个updateContent动作都是一定会执行的*/ var onComplete = this.options.onComplete || Prototype.emptyFunction; this.options.onComplete = (function(transport, param) { this.updateContent(); onComplete(transport, param); }).bind(this); this.request(url); }, updateContent: function() { var receiver = this.container[this.success() ? 'success' : 'failure']; var response = this.transport.responseText; /*如果用户没有在options中指定evalScripts属性,会自动过滤掉返回结果中的<script...></script>标签*/ if (!this.options.evalScripts) response = response.stripScripts(); if (receiver = $(receiver)) { if (this.options.insertion) new this.options.insertion(receiver, response); else receiver.update(response); } if (this.success()) { if (this.onComplete) setTimeout(this.onComplete.bind(this), 10); } } }); /*周期性的更新某个DOM*/ Ajax.PeriodicalUpdater = Class.create(); Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { initialize: function(container, url, options) { this.setOptions(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = {}; this.container = container; this.url = url; this.start(); }, start: function() { /*当transport状态变为complete的时候调用updateComplete方法*/ this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop: function() { this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, /*这个方法主要有两个作用: 1.保存返回结果,主要是用来比较两次更新的结果,如果结果一直没有发生变化的话,更新的周期会呈指数性越来越长 2.设置一个定时器,在decay*frequency秒后进行下下一次更新 */ updateComplete: function(request) { if (this.options.decay) { this.decay = (request.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = request.responseText; } this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); }, onTimerEvent: function() { this.updater = new Ajax.Updater(this.container, this.url, this.options); } });