在Ajax学习笔记中,已经了解了Ajax跨浏览器的解决方案,以及其基本流程。在此,对zepto.js库中的Ajax模块进行初步了解,源码可以到官网下载。
Ajax核心部分
该模块中,我认为最关键的为两个部分$.ajaxSettings
和$.ajax
。
$.ajaxSettings
$.ajaxSettings
定义了Ajax的默认设置,包括请求方法、各阶段回调函数、允许的MIME类型等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| $.ajaxSettings = { type: 'GET', beforeSend: empty, success: empty, error: empty, complete: empty, context: null, global: true, xhr: function() { return new window.XMLHttpRequest() }, accepts: { script: 'text/javascript, application/javascript', json: jsonType, xml: 'application/xml, text/xml', html: htmlType, text: 'text/plain' }, crossDomain: false, timeout: 0 }
|
$.ajax
$.ajax
方法,它是所有其他Ajax方法的底层实现,比如$.get
、$.post
等。在此,我将源码拆分,一步一步进行分析:
首先,是Ajax参数的设置。用户的设置覆盖默认设置,作为最终的设置。
1 2 3
| var settings = $.extend({}, options || {}) for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
|
对比设置中的url与当前页面url主机名,来判断是否跨域。
如果设置的返回值类型dataType
为jsonp
,或者请求路径中含有callback=?
,那么就通过jsonp获取数据。
1 2 3 4 5 6 7 8 9
| if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && RegExp.$2 != window.location.host var dataType = settings.dataType, hasPlaceholder = /=\?/.test(settings.url) if (dataType == 'jsonp' || hasPlaceholder) { if (!hasPlaceholder) settings.url = appendQuery(settings.url, 'callback=?') return $.ajaxJSONP(settings) }
|
serializeData
方法,会对settings.data
(用户传递的数据)进行处理。若为GET
方法,将会将其转换为正常的请求字符串,附在url后。
1 2
| if (!settings.url) settings.url = window.location.toString() serializeData(settings)
|
之后,根据各参数,创建一个请求头的对象。
其中的xhr.overrideMimeType
是XMLHttpRequest2的方法,用于在发送端指定接受内容的mime-type。可以避免客户端与服务器端编码不同,产生乱码的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var mime = settings.accepts[dataType], baseHeaders = {}, protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol, xhr = $.ajaxSettings.xhr(), abortTimeout if (!settings.crossDomain) baseHeaders['X-Requested-With'] = 'XMLHttpRequest' if (mime) { baseHeaders['Accept'] = mime if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0] xhr.overrideMimeType && xhr.overrideMimeType(mime) } if (settings.contentType || (settings.data && settings.type.toUpperCase() != 'GET')) baseHeaders['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded') settings.headers = $.extend(baseHeaders, settings.headers || {})
|
监听状态改变,设置回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| xhr.onreadystatechange = function() { if (xhr.readyState == 4) { clearTimeout(abortTimeout) var result, error = false if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) { dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type')) result = xhr.responseText try { if (dataType == 'script')(1, eval)(result) else if (dataType == 'xml') result = xhr.responseXML else if (dataType == 'json') result = blankRE.test(result) ? null : JSON.parse(result) } catch (e) { error = e } if (error) ajaxError(error, 'parsererror', xhr, settings) else ajaxSuccess(result, xhr, settings) } else { ajaxError(null, 'error', xhr, settings) } } }
|
建立连接,并设置请求头。
1 2 3 4
| var async = 'async' in settings ? settings.async : true xhr.open(settings.type, settings.url, async) for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name])
|
触发beforeSend
事件与检测是否超时。
1 2 3 4 5 6 7 8 9 10
| if (ajaxBeforeSend(xhr, settings) === false) { xhr.abort() return false } if (settings.timeout > 0) abortTimeout = setTimeout(function() { xhr.onreadystatechange = empty xhr.abort() ajaxError(null, 'timeout', xhr, settings) }, settings.timeout)
|
最后,发送数据。
1 2 3
| xhr.send(settings.data ? settings.data : null) return xhr
|
可见,基本流程,依旧是:
- 建立连接
- 设置回调
- 发送数据
只是,在此基础上,增加了对数据的校验、对跨域的支持、添加请求头、增加各阶段触发的函数等,使整个Ajax请求更加健壮。
其余部分
$.get
、$.post
、$.getJSON
、$.fn.load
等均是以$.ajax
为基础进行的简化抽象。
ajaxStart
、ajaxStop
、ajaxBeforeSend
、ajaxSuccess
、ajaxError
、ajaxComplete
则是对各个阶段触发的回调函数的封装。除触发用户设置的回调函数,还将触发设置在全局的对应的Ajax函数。
$.ajaxJSONP
的原理并不基于Ajax,而是基于动态创建script标签。具体的,等之后总结跨域方法的时候再谈。
小结
只要明白了Ajax的基本流程,再去看各库中的Ajax部分,相信会轻松许多。如若本文有何错误及模糊的地方,恳请指正。