读Zepto源码之Ajax模块
读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本本文阅读的源码为 zepto1.2.0 ajax的事件触发顺序
ajax 方法的参数解释现在还没有讲到
内部方法triggerAndReturnfunction triggerAndReturn(context, eventName, data) { var event = $.Event(eventName) $(context).trigger(event, data) return !event.isDefaultPrevented() }
参数 该方法内部调用了 triggerGlobalfunction triggerGlobal(settings, context, data) { if (settings.global) return triggerAndReturn(context || document, data) } 触发全局事件
ajaxStartfunction ajaxStart(settings) { if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart') } 触发全局的 如果 如果 ajaxStopfunction ajaxStop(settings) { if (settings.global && !(--$.active)) triggerGlobal(settings, 'ajaxStop') } 触发全局 如果 ajaxBeforeSendfunction ajaxBeforeSend(xhr, settings) { var context = settings.context if (settings.beforeSend.call(context, xhr, settings) === false || triggerGlobal(settings, 'ajaxBeforeSend', [xhr, settings]) === false) return false triggerGlobal(settings, 'ajaxSend', settings]) }
这两个事件很相似,只不过 在触发 否则触发 注意这里很巧妙地使用了 如果 否则在触发完 ajaxCompletefunction ajaxComplete(status, settings) { var context = settings.context settings.complete.call(context, status) triggerGlobal(settings, 'ajaxComplete', settings]) ajaxStop(settings) } 触发 在触发 ajaxSuccessfunction ajaxSuccess(data, settings, deferred) { var context = settings.context, status = 'success' settings.success.call(context, data, status, xhr) if (deferred) deferred.resolveWith(context, [data, xhr]) triggerGlobal(settings, 'ajaxSuccess', data]) ajaxComplete(status, settings) } 触发 在触发 如果 在触发完 ajaxErrorfunction ajaxError(error, type, deferred) { var context = settings.context settings.error.call(context, error) if (deferred) deferred.rejectWith(context, error]) triggerGlobal(settings, 'ajaxError', error || type]) ajaxComplete(type, settings) } 触发 在触发事件前,调用配置中的 随后调用 emptyfunction empty() {} 空函数,用来作为回调函数配置的初始值。这样的好处是在执行回调函数时,不需要每次都判断回调函数是否存在。 ajaxDataFilterfunction ajaxDataFilter(data, settings) { if (settings.dataFilter == empty) return data var context = settings.context return settings.dataFilter.call(context, type) } 主要用来过滤请求成功后的响应数据。 如果配置中的 如果有配置 mimeToDataTypevar 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' } 返回 先看看这个函数中使用到的几个正则表达式,
接下来是针对 appendQueryfunction appendQuery(url, query) { if (query == '') return url return (url + '&' + query).replace(/[&?]{1,2}/, '?') } 向 如果 如果 最后调用 拼接出来的 parseArgumentsfunction parseArguments(url, 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 } } 这个方法是用来格式化参数的, serializefunction 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, key) else params.add(key, value) }) } 序列化参数。 要了解这个函数,需要了解 如果参数 遍历需要序列化的对象
如果 否则,用 如果 否则如果 其他情况调用 serializeDatafunction 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 } 序列化参数。 如果 如果为 对外接口$.active$.active = 0 正在请求的 $.ajaxSettings$.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 }
$.paramvar 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, traditional) return params.join('&').replace(/%20/g, '+') }
然后将 接着便是简单的调用 最后将容器中的数据用 $.ajaxJSONPvar 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', options, deferred) } else { ajaxSuccess(responseData[0], 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 } 在分析源码之前,先了解一下
所以 一些变量的定义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) 如果配置中的请求类型没有定义,则直接调用 私有变量用来临时存放配置中的
beforeSendif (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) 如果有设置超时时间,则在请求超时时,触发错误事件。 请求成功或失败$(script).on('load error', errorType){ clearTimeout(abortTimeout) $(script).off().remove() if (e.type == 'error' || !responseData) { ajaxError(null, deferred) } else { ajaxSuccess(responseData[0], deferred) } window[callbackName] = originalCallback if (responseData && $.isFunction(originalCallback)) originalCallback(responseData[0]) originalCallback = responseData = undefined }) 在请求成功或者失败时,先清除请求超时定时器,避免触发超时错误,再将插入页面的 如果请求出错,则调用 如果请求成功,则调用 之前我们把 最后将数据和临时函数 $.ajax
处理默认配置var settings = $.extend({}, options || {}), deferred = $.Deferred && $.Deferred(), urlAnchor, hashIndex for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key] ajaxStart(settings)
用 配置处理完毕后,调用 判断是否跨域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) } 如果跨域
注意到这里的 处理请求地址if (!settings.url) settings.url = window.location.toString() if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex) serializeData(settings) 如果没有配置 如果请求的地址带有 然后调用 处理缓存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())
如果 处理jsonpif ('jsonp' == dataType) { if (!hasPlaceholder) settings.url = appendQuery(settings.url, settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?') return $.ajaxJSONP(settings, deferred) } 判断 如果还没有 如果 如果 否则默认追加
一些变量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)
如果 设置请求头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 如果不是跨域请求时,设置请求头
当 如果有指定 或者 如果有配置 before sendif (ajaxBeforeSend(xhr, settings) === false) { xhr.abort() ajaxError(null, 'abort', deferred) return xhr } 调用 同步和异步请求的处理var async = 'async' in settings ? settings.async : true xhr.open(settings.type, settings.url, async, settings.username, settings.password) 如果有配置 接着调用 创建请求后的配置if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name] for (name in headers) nativeSetHeader.apply(xhr, headers[name]) 如果有配置 再遍历上面配置的 发送请求xhr.send(settings.data ? settings.data : null) 发送请求很简单,调用 请求响应成功后的处理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', deferred) } ajaxSuccess(result, deferred) } else { ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', deferred) } } } readyState
具体见 MDN:XMLHttpRequest.readyState 清理工作xhr.onreadystatechange = empty clearTimeout(abortTimeout) 当 成功状态判断if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) { ... } 这里判断的是 解释一下最后这个条件
直接用本地文件的方式打开,也会出现 处理数据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, 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, deferred) 首先获取 如果数据为 否则,用 在解释数据前,调用 如果数据类型为 这里为什么用 如果 如果为 如果解释出错了,则调用 如果都成功了,则调用 响应出错ajaxError(xhr.statusText || null, deferred) 如果 响应超时if (settings.timeout > 0) abortTimeout = setTimeout(function(){ xhr.onreadystatechange = empty xhr.abort() ajaxError(null, 'timeout', deferred) }, settings.timeout) 如果有设置超时时间,则设置一个定时器,超时时,首先要将 然后调用 $.get$.get = function(/* url,data,success,dataType */){ return $.ajax(parseArguments.apply(null, arguments)) }
$.post$.post = function(/* url,dataType */){ var options = parseArguments.apply(null, arguments) options.type = 'POST' return $.ajax(options) }
$.getJSON$.getJSON = function(/* url,success */){ var options = parseArguments.apply(null, arguments) options.dataType = 'json' return $.ajax(options) }
$.fn.load$.fn.load = function(url, success){ if (!this.length) return this var self = this, parts = url.split(/s/), selector, options = parseArguments(url, 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 }
调用 options.success = function(response){ self.html(selector ? $('<div>').html(response.replace(rscript, "")).find(selector) : response) callback && callback.apply(self, arguments) } 请求成功后,如果有 如果有配置回调函数,则执行回调。 系列文章
参考
License作者:对角另一面 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |