jQuery源码分析系列(34) : Ajax - 预处理jsonp
上一章大概讲了前置过滤器和请求分发器的作用,这一章主要是具体分析每种对应的处理方式
$.ajax()
调用不同类型的响应,被传递到成功处理函数之前,会经过不同种类的预处理(prefilters)。 预处理的类型取决于由更加接近默认的Content-Type响应,但可以明确使用dataType
选项进行设置。如果提供了dataType
选项, 响应的Content-Type头信息将被忽略。
有效的数据类型是text, html, xml, json,jsonp,和 script.
dataType:预期服务器返回的数据类型。如果不指定,jQuery 将自动根据 HTTP 包 MIME 信息来智能判断,比如XML MIME类型就被识别为XML。在1.4中,JSON就会生成一个JavaScript对象,而script则会执行这个脚本。随后服务器端返回的数据会根据这个值解析后,传递给回调函数。可用值:
script 类型
$.ajax({ type : "GET", url : "test.js", dataType : "script", complete: function(jqXHR, status) { console.log(jqXHR, status) } });
根据API的说明可知,如果dataType类型为script的时候,需要处理
1 执行脚本
2 内容当作纯文本返回
3 默认情况下不会通过在URL中附加查询字符串变量 "_=[TIMESTAMP]" 进行自动缓存结果,除非设置了cache
参数为true
4 在远程请求时(不在同一个域下),所有POST请求都将转为GET请求。(因为将使用DOM的script标签来加载)
针对上述四点,我们看看处理的流程
inspectPrefiltersOrTransports(prefilters, s, options, jqXHR);
此时的dataType类型就会经过对应的预处理ajaxPrefilter("script")
cache (默认: true, dataType为"script"和"jsonp"时默认为false
)
jQuery.ajaxPrefilter("script", function(s) { if (s.cache === undefined) { s.cache = false; } if (s.crossDomain) { s.type = "GET"; } });
预处理的处理就是将其缓存为设置为 false ,浏览器将不缓存此页面
这将在请求的URL的查询字符串中追加一个时间戳参数,以确保每次浏览器下载的脚本被重新请求
工作原理是在GET请求参数中附加"_={timestamp}"在请求的地址后面加一个时间戳
if (s.cache === false) { s.url = rts.test(cacheURL) ? // If there is already a '_' parameter, set its value cacheURL.replace(rts, "$1_=" + ajax_nonce++) : // Otherwise add one to the end cacheURL + (ajax_rquery.test(cacheURL) ? "&" : "?") + "_=" + ajax_nonce++; }
此时的 s.url = "test.js?_=1402362401890";
该参数不是其他请求所必须的,除了在IE8中,当一个POST请求一个已经用GET请求过的URL
json jsonp 类型
- "json": 把响应的结果当作 JSON 执行,并返回一个JavaScript对象。在 jQuery 1.4 中,JSON 格式的数据以严格的方式解析,如果格式有错误,jQuery都会被拒绝并抛出一个解析错误的异常。(见json.org的更多信息,正确的JSON格式。)
- 如果指定的是
json
,响应结果作为一个对象,在传递给成功处理函数之前使用jQuery.parseJSON
进行解析。 解析后的JSON对象可以通过该jqXHR
对象的responseJSON
属性获得的。 - json的处理只要是在ajaxConvert方法中把结果给转换成需要是json格式,这是后面的内容,这里主要研究下jsonp的预处理
关于JSONP传送飞机:http://baike.baidu.com/view/2131174.htm
JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。JSON系统开发方法是一种典型的面向数据结构的分析和设计方法,以活动为中心,一连串的活动的顺序组合成一个完整的工作进程。
跨域这个问题的产生根本原因是浏览器的同源策略限制,理解同源策略的限制同源策略是指阻止代码获得或者更改从另一个域名下获得的文件或者信息。也就是说我们的请求地址必须和当前网站的地指相同。同源策略通过隔离来实现对资源的保护。这个策略的历史非常悠久从Netscape Navigator 2.0时代就开始了。
- 解决这个限制的一个相对简单的办法就是在服务器端发送请求,服务器充当一个到达第三方资源的代理中继。虽然是用广泛但是这个方法却不够灵活。
- 另一个办法就是使用框架(frames),将第三方站点的资源包含进来,但是包含进来的资源同样要受到同源策略的限制。
- 有一个很巧妙的办法就是在页面中使用动态代码元素,代码的源指向服务地址并在自己的代码中加载数据。当这些代码加载执行的时候,同源策略就不会起到限制。但是如果代码试图下载文件的时候执行还是会失败,幸运的是,我们可以使用JSON(JavaScript Object Notation)来改进这个应用
JSON和JSONP
与XML相比,JSON是一个轻量级的数据交换格式。JSON对于JavaScript开发人员充满魅力的原因在于JSON本身就是Javascript中的对象。
例如一个ticker对象
var ticker = {symbol:'IBM',price:100}
而JSON串就是 {symbol:'IBM',price:100}
这样我们就可以在函数的参数中传递JSON数据。我们很容易掌握在函数中使用动态的JSON参数数据,但是我们的目的并不是这个。
通过使我们的函数能够加载动态的JSON数据,我们就能够处理动态的数据,这项技术叫做 Dynamic Javascript Insertion。
index.html 中
function showPrice(data){ alert("Symbol:" + data.symbol + ", Price:" + data.price); }
然后动态加载ticker.js脚本
var data = {symbol:'IBM', price:100}; showPrice(data);
代码通过动态加入Javascript代码,来执行函数加载数据
正如之前提到过的,同源策略对于动态插入的代码不适用。也就是你可以从不同的域中加载代码,来执行在他们代码中的JSON数据。
这就是JSONP(JSON with Padding)。注意,使用这种方法时,你必须在页面中定义回调函数,就像上例中的showPrice一样。
我们通常所说的JSONP服务(远程JSON服务),实际上就是一种扩展的支持在用户定义函数中包含返回数据的能力。这种方法依赖于必须接受一个回调函数的名字作为参数。
然后执行这个函数,处理JSON数据,并显示在客户页面上。
JSONP的客户端具体实现:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript"src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script>
alert(jQuery)
</script>
</head>
<body>
</body>
</html>
通过script是src加载远程的jQuery毫无疑问是可以正常运行的,所以不难发现Web页面上调用js文件时则不受是否跨域的影响
当然不仅如此,我们还发现凡是拥有"src"这个属性的标签都拥有跨域的能力,比如<script>、<img>、<iframe>等
在进一步我们换成契约式接口
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script type="text/javascript"src="http://code.jquery.com/jquery-1.11.1.min.js"></script> <script type="text/javascript"> function remoteLoad(data){ alert(data) //远程数据 } </script> </head> <body> </body> </html> http://code.jquery.com/jquery-1.11.1.min.js 文件中执行 remoteLoad('加载的数据')
显而易见OK了,通过加载远程的脚本到本地中执行,很好的绕开了跨域的问题了,但是这样的请求是有问题的,接口是契约式的?
怎么让远程js知道它应该调用的本地函数叫什么名字呢?毕竟是jsonp的服务者都要面对很多服务对象,而这些服务对象各自的本地函数都不相同啊?我们接着往下看。
更进一步增加动态回调
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript"> var remoteLoad= function(data){}; var url = "http://code.jquery.com/jquery-1.11.1.min.js?code=1111&callback=remoteLoad"; var script = document.createElement('script'); script.setAttribute('src', url); document.getElementsByTagName('head')[0].appendChild(script); </script> </head> <body> </body>
不再直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分,本例中的重点也就在于如何完成jsonp调用的全过程。
我们看到调用的url中传递了一个callback参数则告诉服务器,我的本地回调函数叫做remoteLoad,所以请把查询结果传入这个函数中进行调用。
所以总结其实json的一个核心点:允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
基本原理OK了,我们看看jQuery的实现,其实也大同小异
$.ajax({ url : "remoteLoad.js", dataType : "jsonp", jsonp : "callback", //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback) jsonpCallback : "Handler", //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据 success: function(data) { console.log(arguments) } });
jQuery的区别最大的不同的就自动帮你生成回调函数并把数据取出来供success属性方法来调用,不是传递的一个回调句柄
篇幅比较长了了 下章再合并讲解内部实现及请求分发器