跨域请求之JSONP

 

跨域请求的方式有很多种,

1,iframe

2,document.domain

3,window.name

4,script

5,XDomainRequest (IE8+)

6,XMLHTTPRequest (Firefox3.5+)

7,postMessage (HTML5)

8,后台代理

...

它们有各自的优缺点,返回的数据格式也各不同,应根据需求慎重选择。比如iframe返回html片段就比较适合,费老劲用它返回JSON就得不偿失了。这篇开始我将打造一个实用的跨域请求工具Sjax。使用script请求的最大缺点,挑战是错误处理。比如404错误,它不象XMLHTTPRequest能准确的返回状态码404。我把这个放在最后一篇。


本系列主要描述以上列举的方式4,即通过script返回JSON格式数据数据。这种方式现在称为JSONP。JSON是目前前后台沟通使用最流行,也最广泛的格式之一。相对于早期的AJAX返回XML(AJAX中的X就是XML),JSON显得更轻量级,没有多余的Tag标记,解析也是原生的。XML返回到前端后先转成文档,通过DOM API一层层的解析。解析DOM是开销比较大的,尤其在早期的IE版本中(IE6/7/8),core js与dom沟通的代价是很大的。


JSONP的实现思路很简单

1, 前端创建script标记,设置src,添加到head中(你可以往body中添加)。

2, 后台返回一个js变量jsonp,这个jsonp就是请求后的JSON数据。

3, 回调完成后删除script标记(还有一些清理工作如避免部分浏览器内存泄露等)。


接口

Js代码  
  1. Sjax.load(   
  2.     url, // 跨越请求的URL   
  3.     success,  // 回调函数,必须定义一个形参,用于接收后台返回的全局变量jsonp (约定后台返回如jsonp = {...}结构)   
  4.     timestamp, // 传true会加一个时间戳,防止缓存,默认不加   
  5. );  
 


示例:

Js代码  
  1. <!DOCTYPE HTML>   
  2. <html>    
  3. <head>    
  4.     <meta charset="utf-8">    
  5.     <title>sjax_0.1.js by snandy</title>   
  6.     <script src="http://files.cnblogs.com/snandy/sjax_0.1.js"></script>   
  7. </head>    
  8. <body>   
  9. <p id="p1" style="background:gold;"></p>   
  10. <input type="button" value="Get Name" οnclick="clk()"/>   
  11. <script type="text/javascript">   
  12.     function clk(){   
  13.         Sjax.load(   
  14.             'http://files.cnblogs.com/snandy/jsonp.js',    
  15.             function(){   
  16.                 document.getElementById('p1').innerHTML = 'Hi, ' + jsonp.name;   
  17.             }   
  18.         );         
  19.     }   
  20. </script>   
  21. </body>   
  22. </html>  
 

这个html实现一个最简单的前后台交互功能,点击按钮“Get Name”,获取到name后显示在段落P上。

clk函数中调用Sjax.load方法,Sjax中的S指script。之前我的Ajax系列中使用的Ajax命名,这里就使用了Sjax。

请求的后台url是jsonp.js,它返回如下

Js代码  
  1. jsonp = {name:'jack'};  
 

 

因为是测试,这里使用最简单的方式实现。请求的后台其实不必是js文件,可以是php,java等任何后台语言,它们可能会连接数据库进行一系列的业务查询。总之它最后返回的结构必须变量jsonp,这个变量就是一个js对象,至于有多复杂则无需关注。

续上篇,加两个实用功能

1,增加data属性,请求参数
2,增加scope属性,可以让回调函数在指定的上下文中执行

 

接口如下

 

 

Js代码  
  1. Sjax.load(url, {   
  2.     data        // 请求参数 (键值对字符串或js对象)   
  3.     success     // 成功后回调函数   
  4.     scope       // 回调函数执行上下文   
  5.     timestamp   // 是否加时间戳   
  6. });  

 

 

示例

 

Html代码  
  1. <!DOCTYPE HTML>  
  2. <html>    
  3. <head>    
  4.     <meta charset="utf-8">    
  5.     <title>sjax_0.2.js by snandy</title>  
  6.     <script src="http://files.cnblogs.com/snandy/sjax_0.2.js"></script>  
  7. </head>    
  8. <body>  
  9. <input type="button" value="Get Name" onclick="clk()"/>  
  10. <script type="text/javascript">  
  11.     function clk(){   
  12.         Sjax.load('http://files.cnblogs.com/snandy/jsonp.js', {   
  13.             fn : function(){alert(jsonp.name)},   
  14.             data : {a:'1',b:'2',c:[11,22]},   
  15.             timestamp : true   
  16.         });   
  17.     }   
  18. </script>  
  19. </body>  
  20. </html>  

 

script请求返回JSON实际上是脚本注入。它虽然解决了跨域问题,但它不是万能的。

1,不能接受HTTP状态码

2,不能使用POST提交(默认GET)

3,不能发送和接受HTTP头

4,不能设置同步调用(默认异步)

...

其最严重的就是不能提供错误处理,如果请求的代码正常执行那么会得到正确的结果。如果请求失败,如404,500之类,那么可能什么都不会发生。这篇在上一篇的基础上将着重解决JSONP的错误处理。


说可能是因为有些浏览器还是可以提供一些错误处理的。如IE9/10/Firefox/Safari/Chrome都支持script的 onerror事件,如果请求失败,在onerror上可以进行必要的回调处理。但IE6/7/8/Opera却不支持onerror。这就是令人头疼的地方,打造一个完美的Sjax不太容易。

只要解决了IE6/7/8/Opera的onerror,整个就ok了。思路是逆向思维:请求成功则成功回调,否则就是失败回调。不拿onerror说事,因为它压根没onerror。因此只能那onload说事。就好比以下推论:

“你是对的” 推断出 “你没错”

因为我没办法知道你是“错的”。但我知道你是“对的”,只能拿是否对去推断你是否错了。


最后的实现细节如下:

1,IE9/Firefox/Safari/Chrome 成功回调使用onload事件,错误回调使用onerror事件

2,Opera 成功回调也使用onload事件(它压根不支持onreadystatechange),由于其不支持onerror,这里使用了延迟处理。即等待与成功回调success,success后标志位done置为true。failure则不会执行,否则执行。这里延迟的时间取值很有技巧,之前取2秒,在公司测试没问题。但回家用3G无线网络后发现即使所引用的js文件存在,但由于网速过慢,failure还是先执行了,后执行了success。所以这里取 5秒是比较合理的。虽然这种方式间接实现了failure,但不彻底。

3,IE6/7/8成功回调使用onreadystatechange事件,错误回调几乎是很难实现的。令人恶心的是即使请求的资源文件不存在(404)。它的readyState也会经历“loaded”状态。这样你就没法区分请求成功或失败。最后使用前后台一起协调的机制解决最后的这个难题。无论请求成功或失败都让其调用callback(true)。 此时已经将区别成功与失败的逻辑放到了callback中,如果后台没有返回 jsonp则调用failure,否则调用success。


接口:

 

Js代码  
  1. Sjax.load(url, {   
  2.     data        // 请求参数 (键值对字符串或js对象)   
  3.     success     // 请求成功回调函数   
  4.     failure     // 请求失败回调函数   
  5.     scope       // 回调函数执行上下文   
  6.     timestamp   // 是否加时间戳   
  7. });  

 

示例:

Html代码  
  1. <!DOCTYPE HTML>  
  2. <html>    
  3. <head>    
  4.     <meta charset="utf-8">    
  5.     <title>sjax_0.3.js by snandy</title>  
  6.     <script src="http://files.cnblogs.com/snandy/sjax_0.3.js"></script>  
  7. </head>    
  8. <body>  
  9. <input type="button" value="Get Name" onclick="clk()"/>  
  10. <script type="text/javascript">  
  11.     function clk(){   
  12.         Sjax.load('jsonp66.js', {   
  13.             success : function(){alert(jsonp.name)},   
  14.             failure : function(){alert('error');}   
  15.         });   
  16.     }   
  17. </script>  
  18. </body>  
  19. </html>  
<!DOCTYPE HTML>
<html> 
<head> 
	<meta charset="utf-8"> 
	<title>sjax_0.3.js by snandy</title>
	<script src="http://files.cnblogs.com/snandy/sjax_0.3.js"></script>
</head> 
<body>
<input type="button" value="Get Name" οnclick="clk()"/>
<script type="text/javascript">
	function clk(){
		Sjax.load('jsonp66.js', {
			success : function(){alert(jsonp.name)},
			failure : function(){alert('error');}
		});
	}
</script>
</body>
</html>

 

以上html,点击“Get Name”按钮,调用clk函数。因为请求的资源jsonp66.js压根不存在。各浏览器下都会弹出“error”,当然Opera中会延迟一些。好了,本系列结束。

 

相关:

http://stackoverflow.com/questions/3483919/script-onload-onerror-with-iefor-lazy-loading-problems

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值