简要:$.ajax是zepto发送请求的核心方法,$.get,$.post,$.jsonp都是封装了$.ajax方法。$.ajax将jsonp与异步请求的代码格式统一起来,内部主要是先处理url,数据和请求头部然后新建XMLHttpRequest对象发送请求。

代码如下:

/**
* ajax 请求
*/
$.ajax = function(options){
var settings = $.extend({}, options || {}), //创建新的options对象,不影响options的值,创建新的副本
deferred = $.Deferred && $.Deferred(), //设置异步队列
urlAnchor, hashIndex; //未传 $.ajaxSettings里的值,复制$.ajaxSettings的mm值
for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key] //触发全局事件 'ajaxStart',$(document).trigger('ajaxStart')
ajaxStart(settings) //是否设置了跨域,未设置,需通过ip 协议 端口一致来判断跨域
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 /*
* for IE
* a = document.createElement('a')
* <a></a>
* a.href = "/foobar"
* "/foobar"
* a.host
* ""
* a.href
* "http://192.168.1.198/foobar"
* a.href = a.href
* "http://192.168.1.198/foobar"
* a.host
* "http://192.168.1.198:80"
* */ // originAnchor.href = window.location.href;
//通过ip 协议 端口来判断跨域 location.host = host:port
settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host)
} //未设置url,取当前地址栏
if (!settings.url) settings.url = window.location.toString() //如果有hash,截掉hash,因为hash ajax不会传递到后台,舍弃#和#后面的
if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex) //将data进行转换
serializeData(settings) // /\?.+=\?/.test('http://www.zhutao.cn/index.html?a=1?callback=?')
// true
//TODO: /\?.+=\?/.test(settings.url) 有xxx.html?a=1?=cccc类似形式,为jsonp
var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url);
if (hasPlaceholder) dataType = 'jsonp' //不设置缓存,加时间戳 '_=' + Date.now()
// 当settings.cache === null时
if (settings.cache === false || (
(!options || options.cache !== true) &&
('script' == dataType || 'jsonp' == dataType)
)) //Date.now() == 1471504727756
settings.url = appendQuery(settings.url, '_=' + Date.now()) //如果是jsonp,调用$.ajaxJSONP,不走XHR,走script
if ('jsonp' == dataType) {
if (!hasPlaceholder) //判断url是否有类似jsonp的参数
settings.url = appendQuery(settings.url,
settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?')
return $.ajaxJSONP(settings, deferred)
} var mime = settings.accepts[dataType], //媒体类型
headers = { },
//TODO 为什么不直接用 headers[name.toLowerCase()] = [name, value]
setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] }, //设置请求头的方法
//如果URL没协议,读取本地URL的协议
protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
//获取异步传输对象
xhr = settings.xhr(), nativeSetHeader = xhr.setRequestHeader,
abortTimeout //将xhr设为只读Deferred对象,不能更改状态
if (deferred) deferred.promise(xhr) //如果没有跨域
// x-requested-with XMLHttpRequest //表明是AJax异步
//x-requested-with null//表明同步,浏览器工具栏未显示,在后台request可以获取到
if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest')
setHeader('Accept', mime || '*/*') //默认接受任何类型
// 先走||运算,再=
if (mime = settings.mimeType || mime) {
//媒体数据源里对应多个,如 script: 'text/javascript, application/javascript, application/x-javascript',
//设置为最新的写法, text/javascript等都是老浏览废弃的写法
if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]; //对Mozilla的修正
// 来自服务器的响应没有 XML mime-type 头部(header),则一些版本的 Mozilla浏览器不能正常运行。
// 对于这种情况,xhr.overrideMimeType(mime); 语句将覆盖发送给服务器的头部,强制mime 作为 mime-type。*/
xhr.overrideMimeType && xhr.overrideMimeType(mime)
}
//如果不是Get请求,设置Content-Type
//Content-Type: 内容类型 指定响应的 HTTP内容类型。决定浏览器将以什么形式、什么编码读取这个文件. 如果未指定 ContentType,默认为TEXT/HTML。
/**
application/x-www-form-urlencoded:是一种编码格式,窗体数据被编码为名称/值对,是标准的编码格式。
当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2...),然后把这个字串append到url后面,用?分割,加载这个新的url。 当action为post时候,浏览器把form数据封装到http body中,然后发送到server
**/
/**
* 如果有 type=file的话,需要设为multipart/form-data了。浏览器会把整个表单以控件为单位分割,并为每个部分加上 Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件 name)等信息,并加上分割符(boundary)。
*/ // 如果method==get,则请求头部不用设置Content-Type,若method==post,则请求头部的Content-Type默认设置'application/x-www-form-urlencoded'
// 请求头部的Content-Type 表示参数的传递形式
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 xhr.onreadystatechange = function(){
/**
0:请求未初始化(还没有调用 open())。
1:请求已经建立,但是还没有发送(还没有调用 send())。
2:请求已发送,正在处理中(通常现在可以从响应中获取内容头)。
3:请求在处理中;通常响应中已有部分数据可用了,但是服务器还没有完成响应的生成。
4:响应已完成;您可以获取并使用服务器的响应了。
*/
if (xhr.readyState == 4) {
xhr.onreadystatechange = empty
clearTimeout(abortTimeout) //清除超时
var result, error = false //根据状态来判断请求是否成功
//>=200 && < 300 表示成功
//304 文件未修改 成功
//xhr.status == 0 && protocol == 'file:' 未请求,打开的本地文件,非localhost ip形式
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) { //获取媒体类型
//mimeToDataType:转换成易读的类型 html,json,scirpt,xml,text等
dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type')) //响应值
result = xhr.responseText //对响应值,根据媒体类型,做数据转换
try {
// http://perfectionkills.com/global-eval-what-are-the-options/
// (1,eval)(result) (1,eval)这是一个典型的逗号操作符,返回最右边的值
// (1,eval) eval 的区别是:前者是一个值,不可以再覆盖。后者是变量,如var a = 1; (1,a) = 1;会报错;
// (1,eval)(result) eval(result) 的区别是:前者变成值后,只能读取window域下的变量。而后者,遵循作用域链,从局部变量上溯到window域
// 显然(1,eval)(result) 避免了作用域链的上溯操作,性能稍好
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 } //解析出错,抛出 'parsererror'事件
if (error) ajaxError(error, 'parsererror', xhr, settings, deferred) //执行success
else ajaxSuccess(result, xhr, settings, deferred) } else {
//如果请求出错
// xhr.status = 0 / null 执行abort, 其他 执行error
ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred)
}
}
} // 执行请求前置器,若返回false则中断请求
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort()
ajaxError(null, 'abort', xhr, settings, deferred)
return xhr
} // xhrFields 设置 如设置跨域凭证 withCredentials
if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name] //'async' in settings ajaxSetting未设置过async为false,设置过,包括null,都为true
// 这是一个小技巧,默认async为true,若settings里面设置了,则为设置的值
var async = 'async' in settings ? settings.async : true
//准备xhr请求
xhr.open(settings.type, settings.url, async, settings.username, settings.password) //设置请求头
for (name in headers) nativeSetHeader.apply(xhr, headers[name]) //超时处理:设置了settings.timeout,超时后调用xhr.abort()中断请求
if (settings.timeout > 0) abortTimeout = setTimeout(function(){
xhr.onreadystatechange = empty;
xhr.abort()
ajaxError(null, 'timeout', xhr, settings, deferred)
}, settings.timeout) // avoid sending empty string (#319)
// 一般来说 post是settings.data get为null 因为get的查询参数和url一起
xhr.send(settings.data ? settings.data : null)
//这是返回的一个promise
return xhr
}

 大致流程如下:

1:根据$.ajaxSettings 完善settings参数

2:触发全局ajaxStart ,document.trgger("ajaxStart")事件

3:判断是否跨域 ,确定settings.crossDomain的值

这里有一个获取url的协议和host的简单方法:

  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.url带不带host和protocol,最后由urlAnchor.host和urlAnchor.protocol会指出当前settings.url的主机名和协议名
这样也就方便对比请求是否跨域了。简单demo如下:
a = document.createElement('a')
<a></a>
a.href = "/foobar"
"/foobar"
a.host
""
a.href
"http://192.168.1.198/foobar"
a.href = a.href
"http://192.168.1.198/foobar"
a.host
"http://192.168.1.198:80"

4:如果没有设置settings.url  ,则默认window.location,即当前url

5:判断setting.url后面是否有#号,有则去掉#后面的

6:option.processData为true(默认为true,即默认以form-data格式来传值),且option.data为对象。则$.param(option.data);若为get请求,

  则url后面添上序列化的option.data。

/**
* 序列化
* 针对options.data 转换成 a=b&c=1
*/
// 1:序列化options.data,2:如果是get请求,则将data加入到url后面
function serializeData(options) {
//options.processData: 对于非get请求,是否将请求参数options.data转换为字符串,processData = true,无视get或者post
if (options.processData && options.data && $.type(options.data) != "string") //将data数据序列化为字符串, 转换成 a=b&c=1
// options.traditional 决定是否深度序列化
options.data = $.param(options.data, options.traditional) // get请求,将序列化的数据追加到url后面
if (options.data && (!options.type || options.type.toUpperCase() == 'GET'))
options.url = appendQuery(options.url, options.data), options.data = undefined
} /**
* 将查询参数追加到URL后面
* @param url
* @param query 查询参数
* @returns {*}
*/
//('www.zhutao.cn' + '&' + 'param=1').replace(/[&?]{1,2}/, '?');
//"www.zhutao.cn?param=1"
/*
* appendQuery("www.zhutao.cn","name=zt&age=1");
× "www.zhutao.cn?name=zt&age=1"
* appendQuery("www.zhutao.cn?sex=2&ape=3","name=zt&age=1");
× "www.zhutao.cn?sex=2&ape=3&name=zt&age=1"
* */
function appendQuery(url, query) {
if (query == '') return url //replace(/[&?]{1,2}/, '?') 匹配到的第一个[&?]{1,2} 替换成?
return (url + '&' + query).replace(/[&?]{1,2}/, '?')
}

  

7:根据url判断dataType是否为jsonP类型。/\?.+=\?/.test

8:如果设置了缓存settings.catch === false,则url后面添加Date.now() ---->随机数

    Date.now() === new Date().getTime() 

9:如果是jsonp类型,则$.ajaxJson();

10:声明变量mine,header,setHeader(用来设置header对象),protocol(用来设置协议http),xhr,nativeSetHeader(xhr.setRequestHeader),abortTimeout

11:deferred.promise(xhr),xhr继承deferred

12:判断settings.crossDomain,若为true,则代表跨域同步请求,否则为Ajax异步,默认为异步请求 

13:设置头部Accept,信息先存入headers,默认为任何类型,此字段是告诉服务器,客户端接受指定的数据类型

accepts: {
script: 'text/javascript, application/javascript, application/x-javascript',
json: jsonType,
xml: 'application/xml, text/xml',
html: htmlType,
text: 'text/plain'
},
var mime = settings.accepts[dataType], //媒体类型
setHeader('Accept', mime || '*/*')  //默认接受任何类型

14:判断有无mineType,有则强制服务器的头部覆盖返回类型即mineType字段加入到头部,mineType主要是用来兼容浏览器用的

15:根据method,设置content-type,于headers中,在get类型的请求头部中是不需要content-type的

  在request中的content-type表示请求的数据格式,在response中的content-type表示返回的数据格式

16:将settings.header中的属性加入到headers中去

17:设置xhr.onreadystatechange

  1:如果状态为4,则设置onreadystatechange为empty,清除计时器,

  2:读取dataType,获取响应值,格式化返回的数据,触发ajaxSuccess事件

18:触发ajax发送之前的操作,若settings.beforeSend返回false,或者全局ajaxBeforeSend返回false,则触发ajaxError("abort"),停止操作并返回。

19:xhrField设置,设置跨域凭证,xhr[name]=settings.xhrFields[name];

20:xhr.open

21:统一根据headers,设置请求头部

22:设置timeout,请求超时

23:xhr.send

24:返回xhr(其实也是个promise)

zepto对于ajax的高层封装有一定的技巧性:

/**
* 参数转换成ajax格式
* @param url
* @param data
* @param success
* @param dataType
* @returns {{url: *, data: *, success: *, dataType: *}}
*/
function parseArguments(url, data, success, dataType) {
if ($.isFunction(data)) dataType = success, success = data, data = undefined //如果data是function,则认为它是请求成功后的回调
if (!$.isFunction(success)) dataType = success, success = undefined
return {
url: url
, data: data //如果data不是function实例
, success: success
, dataType: dataType //服务器返回的data类型
}
} /**
* 便捷方法 get请求
* @returns {*}
*/
$.get = function(/* url, data, success, dataType */){
return $.ajax(parseArguments.apply(null, arguments))
} /**
* 便捷方法 post请求
* @returns {*}
*/
$.post = function(/* url, data, success, dataType */){
var options = parseArguments.apply(null, arguments)
options.type = 'POST'
return $.ajax(options)
} /**
* 便捷方法 响应数据类型为JSON
* content-type: 'application/json'
* @returns {*}
*/
$.getJSON = function(/* url, data, success */){
var options = parseArguments.apply(null, arguments)
options.dataType = 'json'
return $.ajax(options)
} /**
* 载入远程 HTML 文件代码并插入至 DOM 中
* @param url HTML 网页网址 可以指定选择符,来筛选载入的 HTML 文档,DOM 中将仅插入筛选出的 HTML 代码。语法形如 "url #some > selector"。
* @param data 发送至服务器的 key/value 数据
* @param success 载入成功时回调函数
* @returns {*}
*/
$.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 //parts.length > 1 代表url后面有选择符selector
if (parts.length > 1) options.url = parts[0], selector = parts[1] options.success = function(response){
// response.replace(rscript, "") 过滤出script标签
//$('<div>').html(response.replace(rscript, "")) innerHTML方式转换成DOM
self.html(selector ?
$('<div>').html(response.replace(rscript, "")).find(selector)
: response) //执行回调
callback && callback.apply(self, arguments);
}
$.ajax(options)
return this
}

这里的parseArguments方法是一个很好的对函数参数校验的技巧,这个技巧在zepto里面很常见到。一般的方法是对各个参数是否为null进行校验,利用if,else针对不同的情况调用不同的参数。但这里的parseArguments根据不同的情况调整参数的值。然后统一的调用函数。

对于封装同一个函数的不同高层方法来说,提供一个公共方法来处理参数这是非常好的。

$.fn.load : 这对于异步远程拉取html片段传入到dom中是非常好用的。常见的处理场景是单页面应用。

大致流程如下:

1:parseArguments处理参数;2:判断url是否有selector,有则对其进行处理;3:对回调函数进行一次封装,在返回html片段后,加入到dom中,然后执行回调函数。

具体demo如下:

$("body").load("/pageLoading.html",function(data){console.log(data)});
[<body class=​"background-color-#fff" style=​"overflow-x:​ hidden;​">​…​</body>​]
VM3067:2
<div class="pin-page-loading" >
<div class="opacity-bg" >
</div>
<div class="loading-content">
<div class="spi">
<div class="spinner-container container1">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container2">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container3">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
</div>
<div>
</div> $("body").load("/pageLoading.html .opacity-bg",function(data){console.log(data)});
[<body class=​"background-color-#fff" style=​"overflow-x:​ hidden;​">​…​</body>​]
VM3070:2
<div class="pin-page-loading" >
<div class="opacity-bg" >
</div>
<div class="loading-content">
<div class="spi">
<div class="spinner-container container1">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container2">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container3">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
</div>
<div>
</div>

$("body").html();
     "<div class="opacity-bg">
     </div>"

zepto源码研究 - ajax.js($.ajax具体流程分析)的更多相关文章

  1. zepto源码研究 - fx_methods.js

    简要:依赖fx.js,主要是针对show,hide,fadeIn,fadeOut的封装. 源码如下: // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zept ...

  2. zepto源码研究 - fx.js

    简要:zepto 提供了一个基础方法animate来方便我们运用css动画.主要针对transform,animate以及普通属性(例如left,right,height,width等等)的trans ...

  3. zepto源码研究 - callback.js

    简要:$.Callbacks是一个生成回调管家Callback的工厂,Callback提供一系列方法来管理一个回调列表($.Callbacks的一个私有变量list),包括添加回调函数, 删除回调函数 ...

  4. zepto源码研究 - zepto.js - 1

    简要:网上已经有很多人已经将zepto的源码研究得很细致了,但我还是想写下zepto源码系列,将别人的东西和自己的想法写下来以加深印象也是自娱自乐,文章中可能有许多错误,望有人不吝指出,烦请赐教. 首 ...

  5. zepto源码研究 - deferred.js(jquery-deferred.js)

    简要:zepto的deferred.js 并不遵守promise/A+ 规范,而在jquery v3.0.0中的defer在一定程度上实现了promise/A+ ,因此本文主要研究jquery v3. ...

  6. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二八║ Nuxt 基础:面向源码研究Nuxt.js

    前言 哈喽大家周五好,又是一个开开心心的周五了,接下来就是三天小团圆啦,这里先祝大家节日快乐咯,希望都没有加班哈哈,今天公司发了月饼,嗯~时间来不及了,上周应该搞个活动抽中几个粉丝发月饼的,下次吧,这 ...

  7. zepto源码研究 - ajax.js($.ajaxJSONP 的分析)

    简要:jsonp是一种服务器和客户端信息传递方式,一般是利用script元素赋值src来发起请求.一般凡是带有src属性的元素发起的请求都是可以跨域的. 那么jsonp是如何获取服务器的数据的呢? j ...

  8. zepto源码研究 - ajax.js(请求过程中的各个事件分析)

    简要:ajax请求具有能够触发各类事件的功能,包括:触发全局事件,请求发送前事件,请求开始事件,请求结束事件等等,贯穿整个ajax请求过程,这是非常有用的,我们可以利用这些事件来做一些非常有意思的事情 ...

  9. zepto源码研究 - zepto.js - 6(模板方法)

    width  height  模板方法   读写width/height ['width', 'height'].forEach(function(dimension){ //将width,hegih ...

随机推荐

  1. 3527: [Zjoi2014]力

    题目大意:给出n个数qi,定义 Fj为        令 Ei=Fi/qi,求Ei. 设A[i]=q[i],B[i]=1/(i^2). 设C[i]=sigma(A[j]*B[i-j]),D[i]=si ...

  2. 『GitHub』Git常用命令记录

    Commands: git init 把当前目录变成Git可以管理的仓库 随后出现.git目录,这个目录是Git来跟踪管理版本库的git commit -m "change message& ...

  3. Unity3d在安卓android的更新(APK覆盖)

    其实这并没什么技术难点,也不是完美的热更新方案,只能说是退而求其次的一个方法. 起因主要是因为公司几个U3D项目在立项之初都没有能做好热更新的规化,导致现在要去做U3D的热更新非常难,并且项目已处于中 ...

  4. 浅谈Java内存泄露

    一.引言 先等等吧……累了

  5. hdu GCD and LCM

    题意:gcd(a,b,c)=g; lcm(a,b,c)=l; 求出符合的a,b,c的所有情况有多少中. 思路:l/g=p1^x1*p2^x2*p3^x3.....;   x/g=p1^a1*p2^a2 ...

  6. -_-#【AJAX】XMLHttpRequest

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  7. 【数学】XMU 1597 GCD

    题目链接: http://acm.xmu.edu.cn/JudgeOnline/problem.php?id=1597 题目大意: 求(am-bm, an-bn),结果取模1000000007,a,b ...

  8. 安装SQL SERVER2005时,需要win7下安装IIS,记录下

    安装SQL server2005 时,需要先安装IIS,这里描述win7系统下配置IIS的方法. 虽然很多文章都有写过,这里只是重复一下 关键是IIS组件全都勾选上,如果没有全部勾选上,IIS组件没有 ...

  9. Windows 已在 DImageProcess.exe 中触发一个断点。

    其原因可能是堆被损坏,这说明 DImageProcess.exe 中或它所加载的任何 DLL 中有 Bug. 原因也可能是用户在 DImageProcess.exe 具有焦点时按下了 F12. 输出窗 ...

  10. Oracle执行计划——使用index full scan的几种情况

    常见有三种情况都有用到indexfull scan. 1. 查询列就是索引列 2. 对索引列进行order by时 3. 对索列进行聚合计算时