jQuery-1.9.1源码分析系列(十六)ajax——jsonp原理
json jsonp 类型
"json": 把响应的结果当作 JSON 执行,并返回一个JavaScript对象。如果指定的是json
,响应结果作为一个对象,在传递给成功处理函数之前使用jQuery.parseJSON进行解析。 解析后的JSON对象可以通过该jqXHR
对象的responseJSON
属性获得的。json的处理只要是在ajaxConvert方法中把结果给转换成需要是json格式,这是后面的内容,这里主要研究下jsonp的预处理。
"jsonp": JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。
跨域这个问题的产生根本原因是浏览器的同源策略限制,理解同源策略的限制同源策略是指阻止代码获得或者更改从另一个域名下获得的文件或者信息。也就是说我们的请求地址必须和当前网站的地指相同。同源策略通过隔离来实现对资源的保护。这个策略的历史非常悠久从Netscape Navigator 2.0时代就开始了。
解决这个限制的一个相对简单的办法就是在服务器端发送请求,服务器充当一个到达第三方资源的代理中继。虽然是用广泛但是这个方法却不够灵活。
另一个办法就是使用框架(frames),将第三方站点的资源包含进来,但是包含进来的资源同样要受到同源策略的限制。
有一个很巧妙的办法就是在页面中使用动态代码元素,代码的源指向服务地址并在自己的代码中加载数据。当这些代码加载执行的时候,同源策略就不会起到限制。但是如果代码试图下载文件的时候执行还是会失败,幸运的是,我们可以使用JSON(JavaScript Object Notation)来改进这个应用
a. JSONP的客户端具体实现
先看一个简单的例子
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script 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 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 文件中执行(在文档ready之后) remoteLoad('加载的数据')
显而易见是可行的,通过加载远程的脚本到本地中执行,很好的绕开了跨域的问题了,但是这样的请求是有问题的,接口是契约式的?
怎么让远程js知道它应该调用的本地函数叫什么名字呢?毕竟是jsonp的服务者都要面对很多服务对象,而这些服务对象各自的本地函数都不相同啊?我们接着往下看。
更进一步增加动态回调
<!DOCTYPE html> <html> <head> <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> </html>
不再直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分,我们看到调用的url中传递了一个callback参数则告诉服务器,我的本地回调函数叫做remoteLoad,所以请把查询结果传入这个函数中进行调用。
b. jQuery的处理
在jQuery的ajax中jsonp和jsonpCallback分别对应指定“callback”和“remoteLoad”这两个值。
jsonp的默认值为“callback”,jsonpCallback的默认值是一个时间戳字段(jQuery.expando + "_" + ( ajax_nonce++ ))比如“jQuery19107833043907303363_1444871956399”。
我们也可以指定他们,比如{jsonp:”chuaCallBack”,jsonpCallBack:”chuaRemote”}那么在传递的时候url就变成了"http://code.jquery.com/jquery-1.11.1.min.js?..&chuaCallBack=chuaRemote"。
所以总结其实json的一个核心点:允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
$.ajax({ url : "remoteLoad.js", dataType : "jsonp", jsonp : "callback", //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback) jsonpCallback : "Handler", //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据 success: function(data) { console.log(arguments) } });
基本原理OK了,我们看看上面jQuery的使用没有什么太大的区别。
jQuery其实也大同小异,jQuery的区别最大的不同的就自动帮你生成回调函数并把数据取出来供success属性方法来调用,不是传递的一个回调句柄。
jQuery实现jsonp的源码处理步骤如下。
首先,我们需要设置回调函数名称,可以自己定义也可以让jQuery自动设置。
//获取回调函数名称(这个名称可以在ajax的jsonpCallback选项上设置, //否则通过jQuery默认的方式jsonpCallback()来设置) //这个回调函数名称是用来告诉后台需要将返回数据包裹到该函数中,返回前端后执行 callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback;
然后,将回调插入到url或者data选项中(一般来说是URL)
if ( jsonProp ) { s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); } else if ( s.jsonp !== false ) { s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; }
再然后,安装回调
//安装回调 overwritten = window[ callbackName ]; window[ callbackName ] = function() { responseContainer = arguments; };
很明显,本来后台应该执行overwritten这个回调的,但是现在换成了执行后面重写的这个回调,那么在jsonp请求完成后就会执行这个重写方法,给responseContainer赋值了。别急overwritten后面有调用到。
最后,添加延时对象的always响应执行overwritten。
//清除函数(转换完成后执行) jqXHR.always(function() { //保存先前存的值 window[ callbackName ] = overwritten; //将jsonpCallback设置回原始值 if ( s[ callbackName ] ) { //确保重新使用jsonpCallback选项没有杂质 s.jsonpCallback = originalSettings.jsonpCallback; //将callbackName压入oldCallbacks以备将来使用 oldCallbacks.push( callbackName ); } //在请求响应后,如果jsonpCallback指定的回调是一个函数则调用它 if ( responseContainer && jQuery.isFunction( overwritten ) ) { overwritten( responseContainer[ 0 ] ); } responseContainer = overwritten = undefined; });
所以overwritten是在这里面执行的,前面源码中重载过的那个回调在后台调用后获取到了responseContainer值也被用到了。可见我们在设置的jsonpCallback选项指定的回调名(例如chuaRemote)对应的回调先被保存到overwritten中,而这个原始的chuaRemote被赋值为function() { responseContainer = arguments;};
在响应处理中chuaRemote会被调用让responseContainer获取到响应值。最后会执行到jqXHR.always添加的函数处理。将chuaRemote恢复到原来的函数overwritten,并执行overwritten(jsonpCallback指定的回调)。主要是always这个监听处理中还清除callbackName指定的函数,以及添加回到历史等等处理。
好了jsonp就分析到这里了,欢迎拍砖。