Ajax
模块也是常常会用到的模块,Ajax
模块中包含了 jsonp
的现实,和 XMLHttpRequest
的封装。 javascript
读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zeptohtml
本文阅读的源码为 zepto1.2.0前端
zepto
针对 ajax
的发送过程,定义了如下几个事件,正常状况下的触发顺序以下:java
ajaxstart
: XMLHttpRequest
实例化前触发ajaxBeforeSend
: 发送 ajax
请求前触发ajaxSend
: 发送 ajax
请求时触发ajaxSuccess
/ ajaxError
: 请求成功/失败时触发ajaxComplete
: 请求完成(不管成功仍是失败)时触发ajaxStop
: 请求完成后触发,这个事件在 ajaxComplete
后触发。如今尚未讲到 ajax
方法,之因此要将参数提早,是由于后面的内容,不时会用到相关的参数,因此一开始先将参数解释清楚。git
type
: HTTP
请求的类型;url
: 请求的路径;data
: 请求参数;processData
: 是否须要将非 GET
请求的参数转换成字符串,默认为 true
,即默认转换成字符串;contentType
: 设置 Content-Type
请求头;mineType
: 覆盖响应的 MIME
类型,能够是 json
、 jsonp
、 script
、 xml
、 html
、 或者 text
;jsonp
: jsonp
请求时,携带回调函数名的参数名,默认为 callback
;jsonpCallback
: jsonp
请求时,响应成功时,执行的回调函数名,默认由 zepto
管理;timeout
: 超时时间,默认为 0
;headers
:设置 HTTP
请求头;async
: 是否为同步请求,默认为 false
;global
: 是否触发全局 ajax
事件,默认为 true
;context
: 执行回调时(如 jsonpCallbak
)时的上下文环境,默认为 window
。traditional
: 是否使用传统的浅层序列化方式序列化 data
参数,默认为 false
,例若有 data
为 {p1:'test1', p2: {nested: 'test2'}
,在 traditional
为 false
时,会序列化成 p1=test1&p2[nested]=test2
, 在为 true
时,会序列化成 p1=test&p2=[object+object]
;xhrFields
:xhr
的配置;cache
:是否容许浏览器缓存 GET
请求,默认为 false
;username
:须要认证的 HTTP
请求的用户名;password
: 须要认证的 HTTP
请求的密码;dataFilter
: 对响应数据进行过滤;xhr
: XMLHttpRequest
实例,默认用 new XMLHttpRequest()
生成;accepts
:从服务器请求的 MIME
类型;beforeSend
: 请求发出前调用的函数;success
: 请求成功后调用的函数;error
: 请求出错时调用的函数;complete
: 请求完成时调用的函数,不管请求是失败仍是成功。function triggerAndReturn(context, eventName, data) {
var event = $.Event(eventName)
$(context).trigger(event, data)
return !event.isDefaultPrevented()
}复制代码
triggerAndReturn
用来触发一个事件,而且若是该事件禁止浏览器默认事件时,返回 false
。github
参数 context
为上下文,eventName
为事件名,data
为数据。ajax
该方法内部调用了 Event
模块的 trigger
方法,具体分析见《读Zepto源码之Event模块》。正则表达式
function triggerGlobal(settings, context, eventName, data) {
if (settings.global) return triggerAndReturn(context || document, eventName, data)
}复制代码
触发全局事件chrome
settings
为 ajax
配置,context
为指定的上下文对象,eventName
为事件名,data
为数据。json
triggerGlobal
内部调用的是 triggerAndReturn
方法,若是有指定上下文对象,则在指定的上下文对象上触发,不然在 document
上触发。
function ajaxStart(settings) {
if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
}复制代码
触发全局的 ajaxStart
事件。
若是 global
设置为 true
,则 $.active
的值增长1。
若是 global
为 true
,而且 $.active
在更新前的数量为 0
,则触发全局的 ajaxStart
事件。
function ajaxStop(settings) {
if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
}复制代码
触发全局 ajaxStop
事件。
若是 global
为 true
,则将 $.active
的数量减小 1
。若是 $.active
的数量减小至 0
,即没有在执行中的 ajax
请求时,触发全局的 ajaxStop
事件。
function ajaxBeforeSend(xhr, settings) {
var context = settings.context
if (settings.beforeSend.call(context, xhr, settings) === false ||
triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false)
return false
triggerGlobal(settings, context, 'ajaxSend', [xhr, settings])
}复制代码
ajaxBeforeSend
方法,触发 ajaxBeforeSend
事件和 ajaxSend
事件。
这两个事件很类似,只不过 ajaxBeforedSend
事件能够经过外界的配置来取消事件的触发。
在触发 ajaxBeforeSend
事件以前,会调用配置中的 beforeSend
方法,若是 befoeSend
方法返回的为 false
时,则取消触发 ajaxBeforeSend
事件,而且会取消后续 ajax
请求的发送,后面会讲到。
不然触发 ajaxBeforeSend
事件,而且将 xhr
事件,和配置 settings
做为事件携带的数据。
注意这里很巧妙地使用了 ||
进行断路。
若是 beforeSend
返回的为 false
或者触发ajaxBeforeSend
事件的方法 triggerGlobal
返回的为 false
,也即取消了浏览器的默认行为,则 ajaxBeforeSend
方法返回 false
,停止后续的执行。
不然在触发完 ajaxBeforeSend
事件后,触发 ajaxSend
事件。
function ajaxComplete(status, xhr, settings) {
var context = settings.context
settings.complete.call(context, xhr, status)
triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
ajaxStop(settings)
}复制代码
触发 ajaxComplete
事件。
在触发 ajaxComplete
事件前,调用配置中的 complete
方法,将 xhr
实例和当前的状态 state
做为回调函数的参数。在触发完 ajaxComplete
事件后,调用 ajaxStop
方法,触发 ajaxStop
事件。
function ajaxSuccess(data, xhr, settings, deferred) {
var context = settings.context, status = 'success'
settings.success.call(context, data, status, xhr)
if (deferred) deferred.resolveWith(context, [data, status, xhr])
triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data])
ajaxComplete(status, xhr, settings)
}复制代码
触发 ajaxSucess
方法。
在触发 ajaxSuccess
事件前,先调用配置中的 success
方法,将 ajax
返回的数据 data
和当前状态 status
及 xhr
做为回调函数的参数。
若是 deferred
存在,则调用 resoveWith
的方法,由于 deferred
对象,所以在使用 ajax
的时候,可使用 promise
风格的调用。关于 deferred
,见 《读Zepto源码之Deferred模块》的分析。
在触发完 ajaxSuccess
事件后,继续调用 ajaxComplete
方法,触发 ajaxComplete
事件。
function ajaxError(error, type, xhr, settings, deferred) {
var context = settings.context
settings.error.call(context, xhr, type, error)
if (deferred) deferred.rejectWith(context, [xhr, type, error])
triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error || type])
ajaxComplete(type, xhr, settings)
}复制代码
触发 ajaxError
事件,错误的类型能够为 timeout
、error
、 abort
、 parsererror
。
在触发事件前,调用配置中的 error
方法,将 xhr
实例,错误类型 type
和 error
对象做为回调函数的参数。
随后调用 ajaxComplete
方法,触发 ajaxComplete
事件。所以,ajaxComplete
事件不管成功仍是失败都会触发。
function empty() {}复制代码
空函数,用来做为回调函数配置的初始值。这样的好处是在执行回调函数时,不须要每次都判断回调函数是否存在。
function ajaxDataFilter(data, type, settings) {
if (settings.dataFilter == empty) return data
var context = settings.context
return settings.dataFilter.call(context, data, type)
}复制代码
主要用来过滤请求成功后的响应数据。
若是配置中的 dataFilter
属性为初始值 empty
,则将原始数据返回。
若是有配置 dataFilter
,则调用配置的回调方法,将数据 data
和数据类型 type
做为回调的参数,再将执行的结果返回。
var htmlType = 'text/html',
jsonType = 'application/json',
scriptTypeRE = /^(?:text|application)\/javascript/i,
xmlTypeRE = /^(?:text|application)\/xml/i,
function mimeToDataType(mime) {
if (mime) mime = mime.split(';', 2)[0]
return mime && ( mime == htmlType ? 'html' :
mime == jsonType ? 'json' :
scriptTypeRE.test(mime) ? 'script' :
xmlTypeRE.test(mime) && 'xml' ) || 'text'
}复制代码
返回 dataType
的类型。
先看看这个函数中使用到的几个正则表达式,scriptTypeRE
匹配的是 text/javascript
或者 application/javascript
, xmlTypeRE
匹配的是 text/xml
或者 application/xml
, 都还比较简单,不做过多的解释。
Content-Type
的值的形式以下 text/html; charset=utf-8
, 因此若是参数 mime
存在,则用 ;
分割,取第一项,这里是 text/html
,即为包含类型的字符串。
接下来是针对 html
、json
、 script
和 xml
用对应的正则进行匹配,匹配成功,返回对应的类型值,若是都不匹配,则返回 text
。
function appendQuery(url, query) {
if (query == '') return url
return (url + '&' + query).replace(/[&?]{1,2}/, '?')
}复制代码
向 url
追加参数。
若是 query
为空,则将原 url
返回。
若是 query
不为空,则用 &
拼接 query
。
最后调用 replace
,将 &&
、 ?&
,&?
或 ??
替换成 ?
。
拼接出来的 url
的形式如 url?key=value&key2=value
function parseArguments(url, data, success, dataType) {
if ($.isFunction(data)) dataType = success, success = data, data = undefined
if (!$.isFunction(success)) dataType = success, success = undefined
return {
url: url
, data: data
, success: success
, dataType: dataType
}
}复制代码
这个方法是用来格式化参数的,Ajax
模块定义了一些便捷的调用方法,这些调用方法不须要传递 option
,某些必填值已经采用了默认传递的方式,这些方法中有些参数是能够不须要传递的,这个方法就是来用判读那些参数有传递,那些没有传递,而后再将参数拼接成 ajax
所须要的 options
对象。
function serialize(params, obj, traditional, scope){
var type, array = $.isArray(obj), hash = $.isPlainObject(obj)
$.each(obj, function(key, value) {
type = $.type(value)
if (scope) key = traditional ? scope :
scope + '[' + (hash || type == 'object' || type == 'array' ? key : '') + ']'
// handle data in serializeArray() format
if (!scope && array) params.add(value.name, value.value)
// recurse into nested objects
else if (type == "array" || (!traditional && type == "object"))
serialize(params, value, traditional, key)
else params.add(key, value)
})
}复制代码
序列化参数。
要了解这个函数,须要了解 traditional
参数的做用,这个参数表示是否开启以传统的浅层序列化方式来进行序列化,具体的示例见上文参数解释部分。
若是参数 obj
的为数组,则 array
为 true
, 若是为纯粹对象,则 hash
为 true
。 $.isArray
和 $.isPlainObject
的源码分析见《读Zepto源码以内部方法》。
遍历须要序列化的对象 obj
,判断 value
的类型 type
, 这个 type
后面会用到。
scope
是记录深层嵌套时的 key
值,这个 key
值受 traditional
的影响。
若是 traditional
为 true
,则 key
为原始的 scope
值,即对象第一层的 key
值。
不然,用 []
拼接当前循环中的 key
,最终的 key
值会是这种形式 scope[key][key2]...
若是 obj
为数组,而且 scope
不存在,即为第一层,直接调用 params.add
方法,这个方法后面会分析到。
不然若是 value
的类型为数组或者非传统序列化方式下为对象,则递归调用 serialize
方法,用来处理 key
。
其余状况调用 params.add
方法。
function serializeData(options) {
if (options.processData && options.data && $.type(options.data) != "string")
options.data = $.param(options.data, options.traditional)
if (options.data && (!options.type || options.type.toUpperCase() == 'GET' || 'jsonp' == options.dataType))
options.url = appendQuery(options.url, options.data), options.data = undefined
}复制代码
序列化参数。
若是 processData
为 true
,而且参数 data
不为字符串,则调用 $.params
方法序列化参数。 $.params
方法后面会讲到。
若是为 GET
请求或者为 jsonp
,则调用 appendQuery
,将参数拼接到请求地址后面。
$.active = 0复制代码
正在请求的 ajax
数量,初始时为 0
。
$.ajaxSettings = {
// Default type of request
type: 'GET',
// Callback that is executed before request
beforeSend: empty,
// Callback that is executed if the request succeeds
success: empty,
// Callback that is executed the the server drops error
error: empty,
// Callback that is executed on request complete (both: error and success)
complete: empty,
// The context for the callbacks
context: null,
// Whether to trigger "global" Ajax events
global: true,
// Transport
xhr: function () {
return new window.XMLHttpRequest()
},
// MIME types mapping
// IIS returns Javascript as "application/x-javascript"
accepts: {
script: 'text/javascript, application/javascript, application/x-javascript',
json: jsonType,
xml: 'application/xml, text/xml',
html: htmlType,
text: 'text/plain'
},
// Whether the request is to another domain
crossDomain: false,
// Default timeout
timeout: 0,
// Whether data should be serialized to string
processData: true,
// Whether the browser should be allowed to cache GET responses
cache: true,
//Used to handle the raw response data of XMLHttpRequest.
//This is a pre-filtering function to sanitize the response.
//The sanitized response should be returned
dataFilter: empty
}复制代码
ajax
默认配置,这些是 zepto
的默认值,在使用时,能够更改为本身须要的配置。
var escape = encodeURIComponent
$.param = function(obj, traditional){
var params = []
params.add = function(key, value) {
if ($.isFunction(value)) value = value()
if (value == null) value = ""
this.push(escape(key) + '=' + escape(value))
}
serialize(params, obj, traditional)
return params.join('&').replace(/%20/g, '+')
}复制代码
param
方法用来序列化参数,内部调用的是 serialize
方法,而且在容器 params
上定义了一个 add
方法,供 serialize
调用。
add
方法比较简单,首先判断值 value
是否为 function
,若是是,则经过调用函数来取值,若是为 null
或者 undefined
,则 value
赋值为空字符串。
而后将 key
和 value
用 encodeURIComponent
编码,用 =
号链接起来。
接着即是简单的调用 serialize
方法。
最后将容器中的数据用 &
链接起来,而且将空格替换成 +
号。
var jsonpID = +new Date()
$.ajaxJSONP = function(options, deferred){
if (!('type' in options)) return $.ajax(options)
var _callbackName = options.jsonpCallback,
callbackName = ($.isFunction(_callbackName) ?
_callbackName() : _callbackName) || ('Zepto' + (jsonpID++)),
script = document.createElement('script'),
originalCallback = window[callbackName],
responseData,
abort = function(errorType) {
$(script).triggerHandler('error', errorType || 'abort')
},
xhr = { abort: abort }, abortTimeout
if (deferred) deferred.promise(xhr)
$(script).on('load error', function(e, errorType){
clearTimeout(abortTimeout)
$(script).off().remove()
if (e.type == 'error' || !responseData) {
ajaxError(null, errorType || 'error', xhr, options, deferred)
} else {
ajaxSuccess(responseData[0], xhr, options, deferred)
}
window[callbackName] = originalCallback
if (responseData && $.isFunction(originalCallback))
originalCallback(responseData[0])
originalCallback = responseData = undefined
})
if (ajaxBeforeSend(xhr, options) === false) {
abort('abort')
return xhr
}
window[callbackName] = function(){
responseData = arguments
}
script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName)
document.head.appendChild(script)
if (options.timeout > 0) abortTimeout = setTimeout(function(){
abort('timeout')
}, options.timeout)
return xhr
}复制代码
在分析源码以前,先了解一下 jsonp
的原理。
jsonp
实现跨域实际上是利用了 script
能够请求跨域资源的特色,因此实现 jsonp
的基本步骤就是向页面动态插入一个 script
标签,在请求地址上带上须要传递的参数,后端再将数据返回,前端调用回调函数进行解释。
因此 jsonp
本质上是一个 GET
请求,由于连接的长度有限制,所以请求所携带的参数的长度也会有限制。
if (!('type' in options)) return $.ajax(options)
var _callbackName = options.jsonpCallback,
callbackName = ($.isFunction(_callbackName) ?
_callbackName() : _callbackName) || ('Zepto' + (jsonpID++)),
script = document.createElement('script'),
originalCallback = window[callbackName],
responseData,
abort = function(errorType) {
$(script).triggerHandler('error', errorType || 'abort')
},
xhr = { abort: abort }, abortTimeout
if (deferred) deferred.promise(xhr)复制代码
若是配置中的请求类型没有定义,则直接调用 $.ajax
方法,这个方法是整个模块的核心,后面会讲到。 jsonp
请求的 type
必须为 jsonp
。
私有变量用来临时存放配置中的 jsonpCallback
,即 jsonp
请求成功后执行的回调函数名,该配置能够为 function
类型。
callbackName
是根据配置得出的回调函数名。若是 _callbackName
为 function
,则以执行的结果做为回调函数名,若是 _callbackName
没有配置,则用 Zepto
+ 时间戳
做为回调函数名,时间戳初始化后,采用自增的方式来实现函数名的惟一性。
script
用来保存建立的 script
节点。
originalCallback
用来储存原始的回调函数。
responseData
为响应的数据。
abort
函数用来停止 jsonp
请求,实质上是触发了 error
事件。
xhr
对象只有 abort
方法,若是存在 deferred
对象,则调用 promise
方法在 xhr
对象的基础上生成一个 promise
对象。
abortTimeout
用来指定超时时间。
if (ajaxBeforeSend(xhr, options) === false) {
abort('abort')
return xhr
}复制代码
在发送 jsonp
请求前,会调用 ajaxBeforeSend
方法,若是返回的为 false
,则停止 jsonp
请求的发送。
window[callbackName] = function(){
responseData = arguments
}
script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName)
document.head.appendChild(script)复制代码
发送请求前,重写了 window[callbackName]
函数,将 arguments
赋值给 responseData
, 这个函数会在后端返回的 js
代码中执行,这样 responseData
就能够获取获得数据了。
接下来,将 url
的=?
占位符,替换成回调函数名,最后将 script
插入到页面中,发送请求。
if (options.timeout > 0) abortTimeout = setTimeout(function(){
abort('timeout')
}, options.timeout)复制代码
若是有设置超时时间,则在请求超时时,触发错误事件。
$(script).on('load error', function(e, errorType){
clearTimeout(abortTimeout)
$(script).off().remove()
if (e.type == 'error' || !responseData) {
ajaxError(null, errorType || 'error', xhr, options, deferred)
} else {
ajaxSuccess(responseData[0], xhr, options, deferred)
}
window[callbackName] = originalCallback
if (responseData && $.isFunction(originalCallback))
originalCallback(responseData[0])
originalCallback = responseData = undefined
})复制代码
在请求成功或者失败时,先清除请求超时定时器,避免触发超时错误,再将插入页面的 script
从页面上删除,由于数据已经获取到,再也不须要这个 script
了。注意在删除 script
前,调用了 off
方法,将 script
上的事件都移除了。
若是请求出错,则调用 ajaxError
方法。
若是请求成功,则调用 ajaxSuccess
方法。
以前咱们把 window[callbackName]
重写掉了,目的是为了获取到数据,如今再从新将原来的回调函数赋值回去,在获取到数据后,若是 originalCallback
有定义,而且为函数,则将数据做为参数传递进去,执行。
最后将数据和临时函数 originalCallback
清理。
$.ajax
方法是整个模块的核心,代码太长,就不所有贴在这里了,下面一部分一部分来分析。
var settings = $.extend({}, options || {}),
deferred = $.Deferred && $.Deferred(),
urlAnchor, hashIndex
for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
ajaxStart(settings)复制代码
settings
为所传递配置的副本。
deferred
为 deferred
对象。
urlAnchor
为浏览器解释的路径,会用来判断是否跨域,后面会讲到。
hashIndex
为路径中 hash
的索引。
用 for ... in
去遍历 $.ajaxSettings
,做为配置的默认值。
配置处理完毕后,调用 ajaxStart
函数,触发 ajaxStart
事件。
originAnchor = document.createElement('a')
originAnchor.href = window.location.href
if (!settings.crossDomain) {
urlAnchor = document.createElement('a')
urlAnchor.href = settings.url
// cleans up URL for .href (IE only), see https://github.com/madrobby/zepto/pull/1049
urlAnchor.href = urlAnchor.href
settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host)
}复制代码
若是跨域 crossDomain
没有设置,则须要检测请求的地址是否跨域。
originAnchor
是当前页面连接,总体思路是建立一个 a
节点,将 href
属性设置为当前请求的地址,而后获取节点的 protocol
和 host
,看跟当前页面的连接用一样方式拼接出来的地址是否一致。
注意到这里的 urlAnchor
进行了两次赋值,这是由于 ie
默认不会对连接 a
添加端口号,可是会对 window.location.href
添加端口号,若是端口号为 80
时,会出现不一致的状况。具体见:pr#1049
if (!settings.url) settings.url = window.location.toString()
if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex)
serializeData(settings)复制代码
若是没有配置 url
,则用当前页面的地址做为请求地址。
若是请求的地址带有 hash
, 则将 hash
去掉,由于 hash
并不会传递给后端。
而后调用 serializeData
方法来序列化请求参数 data
。
var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url)
if (hasPlaceholder) dataType = 'jsonp'
if (settings.cache === false || (
(!options || options.cache !== true) &&
('script' == dataType || 'jsonp' == dataType)
))
settings.url = appendQuery(settings.url, '_=' + Date.now())复制代码
hasPlaceholder
的正则匹配规则跟上面分析到 jsonp
的替换 callbackName
的正则同样,约定以这样的方式来替换 url
中的 callbackName
。所以,也能够用这样的正则来判断是否为 jsonp
。
若是 cache
的配置为 false
,或者在 dataType
为 script
或者 jsonp
的状况下, cache
没有设置为 true
时,表示不须要缓存,清除浏览器缓存的方式也很简单,就是往请求地址的后面加上一个时间戳,这样每次请求的地址都不同,浏览器天然就没有缓存了。
if ('jsonp' == dataType) {
if (!hasPlaceholder)
settings.url = appendQuery(settings.url,
settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')
return $.ajaxJSONP(settings, deferred)
}复制代码
判断 dataType
的类型为 jsonp
时,会对 url
进行一些处理。
若是尚未 ?=
占位符,则向 url
中追加占位符。
若是 settings.jsonp
存在,则追加 settings.jsonp
+ =?
。
若是 settings.jsonp
为 false
, 则不向 url
中追加东西。
不然默认追加 callback=?
。
url
拼接完毕后,调用 $.ajaxJSONP
方法,发送 jsonp
请求。
var mime = settings.accepts[dataType],
headers = { },
setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] },
protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
xhr = settings.xhr(),
nativeSetHeader = xhr.setRequestHeader,
abortTimeout
if (deferred) deferred.promise(xhr)复制代码
mime
获取数据的 mime
类型。
headers
为请求头。
setHeader
为设置请求头的方法,实际上是往 headers
上增长对应的 key
value
值。
protocol
为协议,匹配一个或多个以字母、数字或者 -
开头,而且后面为 ://
的字符串。优先从配置的 url
中获取,若是没有配置 url
,则取 window.location.protocol
。
xhr
为 XMLHttpRequest
实例。
nativeSetHeader
为 xhr
实例上的 setRequestHeader
方法。
abortTimeout
为超时定时器的 id
。
若是 deferred
对象存在,则调用 promise
方法,以 xhr
为基础生成一个 promise
。
if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest')
setHeader('Accept', mime || '*/*')
if (mime = settings.mimeType || mime) {
if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
xhr.overrideMimeType && xhr.overrideMimeType(mime)
}
if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET'))
setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded')
if (settings.headers) for (name in settings.headers) setHeader(name, settings.headers[name])
xhr.setRequestHeader = setHeader复制代码
若是不是跨域请求时,设置请求头 X-Requested-With
的值为 XMLHttpRequest
。这个请求头的做用是告诉服务端,这个请求为 ajax
请求。
setHeader('Accept', mime || '*/*')
用来设置客户端接受的资源类型。
当 mime
存在时,调用 overrideMimeType
方法来重写 response
的 content-type
,使得服务端返回的类型跟客户端要求的类型不一致时,能够按照指定的格式来解释。具体能够参见这篇文章 《你真的会使用XMLHttpRequest吗?》。
若是有指定 contentType
,
或者 contentType
没有设置为 false
,而且 data
存在以及请求类型不为 GET
时,设置 Content-Type
为指定的 contentType
,在没有指定时,设置为 application/x-www-form-urlencoded
。因此没有指定 contentType
时, POST
请求,默认的 Content-Type
为 application/x-www-form-urlencoded
。
若是有配置 headers
,则遍历 headers
配置,分别调用 setHeader
方法配置。
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort()
ajaxError(null, 'abort', xhr, settings, deferred)
return xhr
}复制代码
调用 ajaxBeforeSend
方法,若是返回的为 false
,则停止 ajax
请求。
var async = 'async' in settings ? settings.async : true
xhr.open(settings.type, settings.url, async, settings.username, settings.password)复制代码
若是有配置 async
,则采用配置中的值,不然,默认发送的是异步请求。
接着调用 open
方法,建立一个请求。
if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name]
for (name in headers) nativeSetHeader.apply(xhr, headers[name])复制代码
若是有配置 xhrFields
,则遍历,设置对应的 xhr
属性。
再遍历上面配置的 headers
对象,调用 setRequestHeader
方法,设置请求头,注意这里的请求头必需要在 open
以后,在 send
以前设置。
xhr.send(settings.data ? settings.data : null)复制代码
发送请求很简单,调用 xhr.send
方法,将配置中的数据传入便可。
xhr.onreadystatechange = function(){
if (xhr.readyState == 4) {
xhr.onreadystatechange = empty
clearTimeout(abortTimeout)
var result, error = false
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type'))
if (xhr.responseType == 'arraybuffer' || xhr.responseType == 'blob')
result = xhr.response
else {
result = xhr.responseText
try {
// http://perfectionkills.com/global-eval-what-are-the-options/
// sanitize response accordingly if data filter callback provided
result = ajaxDataFilter(result, dataType, settings)
if (dataType == 'script') (1,eval)(result)
else if (dataType == 'xml') result = xhr.responseXML
else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result)
} catch (e) { error = e }
if (error) return ajaxError(error, 'parsererror', xhr, settings, deferred)
}
ajaxSuccess(result, xhr, settings, deferred)
} else {
ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred)
}
}
}复制代码
readyState
有如下5种状态,状态切换时,会响应 onreadystatechange
的回调。
0 | xhr 实例已经建立,可是尚未调用 open 方法。 |
---|---|
1 | 已经调用 open 方法 |
2 | 请求已经发送,能够获取响应头和状态 status |
3 | 下载中,部分响应数据已经可使用 |
4 | 请求完成 |
具体见 MDN:XMLHttpRequest.readyState
xhr.onreadystatechange = empty
clearTimeout(abortTimeout)复制代码
当 readyState
变为 4
时,表示请求完成(不管成功仍是失败),这时须要将 onreadystatechange
从新赋值为 empty
函数,清除超时响应定时器,避免定时器超时的任务执行。
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
...
}复制代码
这里判断的是 http
状态码,状态码的含义能够参考 HTTP response status codes。
解释一下最后这个条件 xhr.status == 0 && protocol == 'file:'
。
status
为 0
时,表示请求并无到达服务器,有几种状况会形成 status
为 0
的状况,例如网络不通,不合法的跨域请求,防火墙拦截等。
直接用本地文件的方式打开,也会出现 status
为 0
的状况,可是我在 chrome
上测试,在这种状况下只能取到 status
, responseType
和 responseText
都取不到,不清楚这个用本地文件打开时,进入成功判断的目的何在。
blankRE = /^\s*$/,
dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type'))
if (xhr.responseType == 'arraybuffer' || xhr.responseType == 'blob')
result = xhr.response
else {
result = xhr.responseText
try {
// http://perfectionkills.com/global-eval-what-are-the-options/
// sanitize response accordingly if data filter callback provided
result = ajaxDataFilter(result, dataType, settings)
if (dataType == 'script') (1,eval)(result)
else if (dataType == 'xml') result = xhr.responseXML
else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result)
} catch (e) { error = e }
if (error) return ajaxError(error, 'parsererror', xhr, settings, deferred)复制代码
首先获取 dataType
,后面会根据 dataType
来判断得到的数据类型,进而调用不一样的方法来处理。
若是数据为 arraybuffer
或 blob
对象时,即为二进制数据时,result
从 response
中直接取得。
不然,用 responseText
获取数据,而后再对数据尝试解释。
在解释数据前,调用 ajaxDataFilter
对数据进行过滤。
若是数据类型为 script
,则使用 eval
方法,执行返回的 script
内容。
这里为何用 (1, eval)
,而不是直接用 eval
呢,是为了确保 eval
执行的做用域是在 window
下。具体参考:(1,eval)('this') vs eval('this') in JavaScript? 和 《Global eval. What are the options?》
若是 dataType
为 xml
,则调用responseXML
方法
若是为 json
,返回的内容为空时,结果返回 null
,若是不为空,调用 $.parseJSON
方法,格式化为 json
格式。相关分析见《读zepto源码之工具函数》
若是解释出错了,则调用 ajaxError
方法,触发 ajaxError
事件,事件类型为 parseerror
。
若是都成功了,则调用 ajaxSuccess
方法,执行成功回调。
ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred)复制代码
若是 status
不在成功的范围内,则调用 ajaxError
方法,触发 ajaxError
事件。
if (settings.timeout > 0) abortTimeout = setTimeout(function(){
xhr.onreadystatechange = empty
xhr.abort()
ajaxError(null, 'timeout', xhr, settings, deferred)
}, settings.timeout)复制代码
若是有设置超时时间,则设置一个定时器,超时时,首先要将 onreadystatechange
的回调设置为空函数 empty
,避免超时响应执行完毕后,请求完成,再次执行成功回调。
而后调用 xhr.abort
方法,取消请求的发送,而且调用 ajaxError
方法,触发 ajaxError
事件。
$.get = function(/* url, data, success, dataType */){
return $.ajax(parseArguments.apply(null, arguments))
}复制代码
$.get
是 $.ajax
GET
请求的便捷方法,内部调用了 $.ajax
,不须要指定请求类型。
$.post = function(/* url, data, success, dataType */){
var options = parseArguments.apply(null, arguments)
options.type = 'POST'
return $.ajax(options)
}复制代码
$.post
是 $.ajax
POST
请求的便捷方法,跟 $.get
同样,只开放了 url
、data
、success
和 dataType
等几个接口参数,默认配置了 type
为 POST
请求。
$.getJSON = function(/* url, data, success */){
var options = parseArguments.apply(null, arguments)
options.dataType = 'json'
return $.ajax(options)
}复制代码
$.getJSON
跟 $.get
差很少,比 $.get
更省了一个 dataType
的参数,这里指定了 dataType
为 json
类型。
$.fn.load = function(url, data, success){
if (!this.length) return this
var self = this, parts = url.split(/\s/), selector,
options = parseArguments(url, data, success),
callback = options.success
if (parts.length > 1) options.url = parts[0], selector = parts[1]
options.success = function(response){
self.html(selector ?
$('<div>').html(response.replace(rscript, "")).find(selector)
: response)
callback && callback.apply(self, arguments)
}
$.ajax(options)
return this
}复制代码
load
方法是用 ajax
的方式,请求一个 html
文件,并将请求的文件插入到页面中。
url
能够指定选择符,选择符用空格分割,若是有指定选择符,则只将匹配选择符的文档插入到页面中。url
的格式为 请求地址 选择符
。
var self = this, parts = url.split(/\s/), selector,
options = parseArguments(url, data, success),
callback = options.success
if (parts.length > 1) options.url = parts[0], selector = parts[1]复制代码
parts
是用空格分割后的结果,若是有选择符,则 length
会大于 1
,数组的第一项为请求地址,第二项为选择符。
调用 parseArguments
用来从新调整参数,由于 data
和 success
都是可选的。
options.success = function(response){
self.html(selector ?
$('<div>').html(response.replace(rscript, "")).find(selector)
: response)
callback && callback.apply(self, arguments)
}复制代码
请求成功后,若是有 selector
,则从文档中筛选符合的文档插入页面,不然,将返回的文档所有插入页面。
若是有配置回调函数,则执行回调。
最后,全部文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
做者:对角另外一面