jQuery源码分析(二十二): ajax

2016-12-26 09:27:01来源:作者:栖迟於一丘人点击

$.ajax做了哪些事

标准的w3c直接提供了 XMLHttpRequest 方法,而从低耦合高内聚:

提供快捷接口提供底层接口提供数据序列化提供全局 Ajax 事件处理

看一个例子

//全局事件触发$(document).ajaxStart(function() { console.log(1);}).ajaxComplete(function() { console.log(4);});$(".trigger").click(function() { //发送ajax请求 $.ajax({ url: "/", context: document.body, complete: function() { console.log(3); } }).done(function() { console.log(2); });});

给document绑定 ajaxStart , ajaxComplete 回调事件, trigger 绑定一个点击事件,发送ajax请求,点击trigger出发点之后,发送一个ajax请求,并且通过 complete , done ,ajaxStart, ajaxComplete返回状态回调。

有两种回调方式,内部的complete回调与外部的done回调。而全局document上都能捕获到ajax的每一步的回调通知。

补充:内部回调 beforeSend, error, dataFilter, success 和 complete等;外部回调done、fail、when、always 等。回调都是基于 deferred 方式的 done 回调。

如果自己设计,要求也是满足链式:

function my_ajax(config) { var doneFn; var url = config.url; var complete = config.complete; var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); xhr.open('get', url); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { doneFn(xhr.responseText); complete(xhr.responseText); } } }; xhr.send(xhr.responseText); return { /** * 返回一个done对象 */ done: function(ourfn) { doneFn = ourfn; } };}my_ajax({ url:"/", complete:function (data) { console.log("t2"); }}).done(function () { console.log("t1");});

jq的实现是更复杂的,因为它需要考虑到:跨域,json格式,数据类型,乱码,页面缓存、状态跟踪,浏览器兼容。

ajax的异步实现

jq的代码中关于ajax的部分有1000多行,主要针对ajax的操作进行了一些扩展,让它更加灵活。提供了三个方法用于管理、扩展ajax请求:

前置过滤器 jQuery. ajaxPrefilter请求分发器 jQuery. ajaxTransport类型转换器 ajaxConvert

除此之后还重写了整个异步队列处理,加入了 deferred ,可以将任务完成的处理方式与任务本身解耦合,使用 deferreds 对象,多个回调函数可以被绑定在任务完成时执行,甚至可以在任务完成后绑定这些回调函数。这些任务可以是异步的,也可以是同步的。

deferred.promise 方法就是把普通对象转化成 deferred 对象了, 而 Promise 函数的返回值是 deferred 对象的一个只读视图。

ajax 就是把 deferred 对象给掺进去可以让整个 Ajax 方法变成了一个 deferred 对象,在Ajax方法中返回的是 jqXHR 一个包装对象,在这个对象里面混入了所有实现方法。代码较多,做了一些简化和拆解以便理解。

ajax: function(url, options) { deferred = jQuery.Deferred(); var jqXHR = {}; //ajax对象 //转成deferred对象 deferred.promise(jqXHR).complete = completeDeferred.add; return jqXHR}

通过 deferred.promise(jqXHR) 将对象转成满足 promise 接口规范。jqXHR 对象将公开下列属性和方法:

responseXML / responseText 当底层的请求分别作出XML或文本响应; setRequestHeader(name, value) 从标准出发,通过替换旧的值为新的值,而不是替换的新值到旧值.

给 jqXHR 添加promise的属性和方法后:

deferred.promise( jqXHR ).complete = completeDeferred.add;jqXHR.success = jqXHR.done;jqXHR.error = jqXHR.fail;

添加 complete 方法,这里用的是回调列表的 add 方法(即添加回调).

把用户自定的内部回调函数给注册到 jqXHR 对象上:

// Install callbacks on deferredsfor ( i in { success: 1, error: 1, complete: 1 } ) { jqXHR[ i ]( s[ i ] );}

通过一个 for 循环把对应的方法都执行了。

jqXHR.success(s.success) -> jqXHR.done -> jQuery.Callbacks("once memory")

jqXHR.error(s.error) -> jqXHR.fail -> jQuery.Callbacks("once memory")

jqXHR.complete(s.complete) -> jQuery.Callbacks("once memory").add(s.success)

其中,s的属性有:

options(可选):AJAX请求设置。所有选项都是可选的async(Boolean):(默认:true)默认设置下,所有请求均为异步请求,如果需要发送同步请求,请将此选项设置为false.注意,同步请求将锁住浏览器,用户其它操作等待请求完成才可以执行。beforeSend(Function):发送请求前可修改XMLHttpRequest对象的函数,如添加自定义HTTP头。XMLHttpRequest对象是唯一的参数。Ajax函数cache(boolean): (默认: true,dataType为script时默认为false) jQuery 1.2 新功能,设置为 false 将不会从浏览器缓存中加载 请求信息。complete (Function) : 请求完成后回调函数 (请求成功或失败时均调用)。参数: XMLHttpRequest 对象和一个描述成功请求类型 的字符串。 Ajax 事件。 contentType (String) : (默认: "application/x-www-form-urlencoded") 发送信息至服务器时内容编码类型。默认值适合大多数应用场合。data (Object,String) : 发送到服务器的数据。将自动转换为请求字符串格式。GET 请求中将附加在 URL 后。查看 processData选项说明以禁止此自动转换。必须为 Key/Value 格式。如果为数组,jQuery 将自动为不同值对应同一个名称。如 {foo:["bar1", "bar2"]} 转换为 '&foo=bar1&foo=bar2'。dataFilter (Function) :给Ajax返回的原始数据的进行预处理的函数。提供data和type两个参数:data是Ajax返回的原始数据, type是调用jQuery.ajax时提供的dataType参数。函数返回的值将由jQuery进一步处理。 dataType (String) : 预期服务器返回的数据类型。如果不指定,jQuery 将自动根据 HTTP 包 MIME 信息返回 responseXML 或responseText,并作为回调函数参数传递,可用值: "xml": 返回 XML 文档,可用 jQuery 处理。 "html": 返回纯文本 HTML 信息;包含 script 元素。 "script": 返回纯文本 JavaScript 代码。不会自动缓存结果。除非设置了"cache"参数 "json": 返回 JSON 数据 。 "jsonp": JSONP 格式。使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。 "text": 返回纯文本字符串error (Function) : (默认: 自动判断 (xml 或 html)) 请求失败时调用时间。参数:XMLHttpRequest 对象、错误信息、(可选) 捕获的错误对象。Ajax 事件。 global (Boolean) : (默认: true) 是否触发全局 AJAX 事件。设置为 false 将不会触发全局 AJAX 事件,如 ajaxStart 或ajaxStop 可用于控制不同的 Ajax 事件。ifModified (Boolean) : (默认: false) 仅在服务器数据改变时获取新数据。使用 HTTP 包 Last-Modified 头信息判断。jsonp (String) : 在一个jsonp请求中重写回调函数的名字。这个值用来替代在"callback=?"这种GET或POST请求中URL参数里 的"callback"部分,比如{jsonp:'onJsonPLoad'}会导致将"onJsonPLoad=?"传给服务器。password (String) : 用于响应HTTP访问认证请求的密码processData (Boolean) : (默认: true) 默认情况下,发送的数据将被转换为对象(技术上讲并非字符串) 以配合默认内容类型"application/x-www-form-urlencoded"。如果要发送 DOM 树信息或其它不希望转换的信息,请设置为 false。scriptCharset (String) : 只有当请求时dataType为"jsonp"或"script",并且type是"GET"才会用于强制修改charset。通常在本 地和远程的内容编码不同时使用。success (Function) : 请求成功后回调函数。参数:服务器返回数据,数据格式。 Ajax 事件。 timeout (Number) : 设置请求超时时间(毫秒)。此设置将覆盖全局设置。type (String) : (默认: "GET") 请求方式 ("POST" 或 "GET"), 默认为 "GET"。注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持。url (String) : (默认: 当前页地址) 发送请求的地址。username (String) : 用于响应HTTP访问认证请求的用户名 前置过滤器和请求分发器 ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),//前置过滤器ajaxTransport: addToPrefiltersOrTransports( transports ),//请求分发器

这两个都是为了方便管理的。通过 addToPrefiltersOrTransports() ,就是把对应的方法制作成函数的形式填充到 prefilters 或者 transports对应的处理包装对象中,它可以理解为:

var addToPrefiltersOrTransports = function(structure) { return function(func) { while ( (dataType = dataTypes[i++]) ) { structure[ dataType ] = func; } }}

用的时候直接执行,每个函数都保持着各自的引用,种写法的好处自然是灵活,易维护,减少代码量。

所以此时的 prefilters 中的结构可以是这样:

prefilters = { '*': function() { return { send: function() { }, callback: function() { } } }}

prefilters 中的前置过滤器在请求发送之前、设置请求参数的过程中被调用,调用 prefilters 的是函数 inspectPrefiltersOrTransports ,巧妙的是 transports 中的请求分发器在大部分参数设置完成后,也通过函数 inspectPrefiltersOrTransports 取到与请求类型匹配的请求分发器。

通过这种手段,我们可以自己定义数据类型的处理,如 “mytype” :

//////////////////////////////////////////////////////////////////// options 是请求的选项 //// originalOptions值作为提供给Ajax方法未经修改的选项, //// 因此,没有ajaxSettings设置中的默认值 //// jqXHR 是请求的jqXHR对象 ////////////////////////////////////////////////////////////////////$.ajaxPrefilter("mytype", function(options, originalOptions, jqXHR) { options.url += '?_=1';//自定义修改设置 console.log("前置过滤");});//////////////////////////// 请求分发器 transports ////////////////////////////$.ajaxTransport("image", function(s) { return { send: function(_, callback) { image = new Image(); function done(status) { if (image) { var statusText = (status == 200) ? "success" : "error", tmp = image; image = image.onreadystatechange = image.onerror = image.onload = null; callback(status, statusText, { image: tmp }); } } image.onreadystatechange = image.onload = function() { done(200); }; image.onerror = function() { done(404); }; show(s.url) image.src = s.url; }, abort: function() { if (image) { image = image.onreadystatechange = image.onerror = image.onload = null; } } };}

使用的时候就可以是:

var ajax = $.ajax({ url : 'a.php', dataType : 'mytype', type : 'POST', data: { foo: ["bar1", "bar2"] }, //这个对象用于设置Ajax相关回调函数的上下文 context: document.body, //请求发送前的回调函数,用来修改请求发送前jqXHR beforeSend: function(xhr) { xhr.overrideMimeType("text/plain; charset=x-user-defined"); console.log('局部事件beforeSend') }, //请求完成后回调函数 (请求success 和 error之后均调用) complete: function() { console.log('局部事件complete') }, error: function() { console.log('局部事件error请求失败时调用此函数') }, success: function() { console.log('局部事件success') }});ajax.done(function() { console.log('done')}).fail(function() { console.log('fail')}).always(function() { console.log('always')});

提供可选的 dataTypes 参数,那么预滤器(prefilter)将只会对满足指定 dataTypes 的请求有效。

$.ajax({ type : "GET", url : "test.js", dataType : "script"});

针对 prefilters 的方法其实就是 dataType 为 script,json,jsonp的处理,当我们动态加载脚本文件。

预处理script类型

jq自身提供了dataTypes,有效类型是: text, html, xml, json,jsonp,script

"script" 和 "jsonp"的时候不能使用缓存,并且将跨域的强制改为get方法:

jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; }});

当不采用页面缓存的时候,jq会在url尾部添加参数:

// Add anti-cache in url if neededif ( s.cache === false ) { s.url = rts.test( cacheURL ) ? // If there is already a '_' parameter, set its value cacheURL.replace( rts, "$1_=" + nonce++ ) : // Otherwise add one to the end cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;}

页面返回的是被当纯文本的,所以想要执行脚本,就需要建立一个 <script> 标签来插入:

// Bind script tag hack transportjQuery.ajaxTransport( "script", function( s ) { // This transport only deals with cross domain requests if ( s.crossDomain ) { var script, callback; return { send: function( _, complete ) { script = jQuery("<script>").prop({ async: true, charset: s.scriptCharset, src: s.url }).on( "load error", callback = function( evt ) { script.remove(); callback = null; if ( evt ) { complete( evt.type === "error" ? 404 : 200, evt.type ); } } ); document.head.appendChild( script[ 0 ] ); }, abort: function() { if ( callback ) { callback(); } } }; }}); json与jsonp

JSON:把响应的结果当作 JSON 执行,并返回一个 JavaScript 对象。如果指定的是 json,响应结果作为一个对象,在传递给成功处理函数之前使用 jQuery.parseJSON 进行解析。 解析后的 JSON 对象可以通过该 jqXHR 对象的 responseJSON 属性获得的。json 的处理只要是在 ajaxConvert 方法中把结果给转换成需要是 json 格式,这是后面的内容,这里主要研究下 jsonp 的预处理。

JSONP:是一个非官方的协议,它允许在服务器端集成 Script tags 返回至客户端,通过javascript callback 的形式实现跨域访问(这仅仅是 JSONP 简单的实现形式)。JSON 系统开发方法是一种典型的面向数据结构的分析和设计方法,以活动为中心,一连串的活动的顺序组合成一个完整的工作进程。

jsonp的出现是为了解决跨域问题,由于浏览器的 同源策略 限制,阻止代码获得或者更改从另一个域名下获得的文件或者信息。就是说我们的请求地址必须和当前网站的地址相同。

一个办法就是使用框架(frames),而更好的方法是使用jsonp。

jsonp与json类型不同的地方就是,jsonp允许用户传递一个 callback 参数给服务端,然后服务端返回数据会callback作为函数名包裹json数据,这样客户端就可以随意定制自己的函数来处理返回数据了。

服务端:

<?phpecho $_GET['callback'].'('. json_encode(array('status'=>1,'info'=>'OK')) .')';

就是执行的 callback 方法,然后把数据通过回调的方式传递过。

//////////////////////////////////// jQuery的调用//////////////////////////////////$.ajax({ crossDomain :true, url: 'http://test.hongweipeng.com/a.php', //不同的域 type: 'GET', // jsonp模式只有GET是合法的 data: { 'action': 'aaron' }, // 预传参的数组 dataType: 'jsonp', // 数据类型 jsonp: 'callback', // 指定回调函数名,与服务器端接收的一致,并回传回来 jsonpCallback:"flightHandler", success: function(json) { console.log(json); }}); jsonp的原理

利用script标签我们也可以实现jsonp的方式,因为 img、iframe、script 等标签可以通过 src 属性请求到其他服务器上的数据:

//////////////////////////////////// jsonp的原理////////////////////////////////////服务器调用的全局函数,用来接受数据function flightHandler(data){ console.log(data)}function createJsonp(url, complete) { var script = jQuery("<script>").prop({ async: true, src: "http://test.hongweipeng.com/a.php?callback=flightHandler&action=aaron&_=1418782732584" }).on( "load error", callback = function(evt) { script.remove(); callback = null; } ); document.head.appendChild(script[0]);}createJsonp();

先append到头部去,待加载后脚本运行完后再 remove() 掉。

jq底层实现jsonp的原理就是采用这种方式,它不是靠 XmlHttpRequest 而是 script ,所以不要被这个方法给迷惑了。

类型转换

服务端放回的只能是 字符串 形式: {"a":1,"b":2,"c":3,"d":4,"e":5} ,如何将它转换为 Object 的形式:

{ a: 1 b: 2 c: 3 d: 4 e: 5} converters的映射 converters: { // Convert anything to text、 // 任意内容转换为字符串 // window.String 将会在min文件中被压缩为 a.String "* text": window.String, // Text to html (true = no transformation) // 文本转换为HTML(true表示不需要转换,直接返回) "text html": true, // Evaluate text as a json expression // 文本转换为JSON "text json": jQuery.parseJSON, // Parse text as xml // 文本转换为XML "text xml": jQuery.parseXML}

如果 dataType 为空,自动转化:

while (dataTypes[0] === "*") { dataTypes.shift(); if (ct === undefined) { ct = s.mimeType || jqXHR.getResponseHeader("Content-Type"); }} 设置超时

默认的jq是不启用超时的,超时就是有可能发生的,不可能请求失败了还让用户在那傻傻等待,是谁都受不了。所以jq巧妙的用了 setTimeout 来设置超时:

// Timeoutif ( s.async && s.timeout > 0 ) { timeoutTimer = setTimeout(function() { jqXHR.abort("timeout"); }, s.timeout );}

最后是调用 XMLHttpRequest 对象的 abort() 方法

if ( type === "abort" ) { xhr.abort();}

而当请求成功时候记得销毁这个定时器:

if ( timeoutTimer ) { clearTimeout( timeoutTimer );} 同步与异步

ajax是异步模型,但也能满足同步的需求,以前我一直纳闷,jq是怎么将它变成同步的,原来非常简单,jq的ajax都是基于 XMLHttpRequest 的,而在它请求里就可以设置是否是异步了:

xhr.open( options.type, options.url, options.async, options.username, options.password );

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台