跨域的异步请求二
说一下JSONP的原理,我们的跨域严重依赖这东西。其实这是一种脚本注入的行为,前后涉及两段脚本片断,并公开一些全局变量。为了放便讲解,我把例子先放出来:
<!doctype html>
<html>
<head>
<title>jsonp原理 by 司徒正美</title>
<meta charset="utf-8"/>
<meta content="IE=8" http-equiv="X-UA-Compatible"/>
<meta name="keywords" content="jsonp原理 by 司徒正美" />
<meta name="description" content="jsonp原理 by 司徒正美" />
<script type="text/javascript" charset="utf-8">
window.onload = function(){
var script = document.createElement("script");
window.jsonpCallBack = function(json){
for(var item in json){
alert(item)
}
if(window.JSON){
alert(window.JSON.stringify(json))
}
}
script.src = "http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=jsonpCallBack"
var head = document.getElementsByTagName("head")[0];
head.appendChild(script);
}
</script>
</head>
<body>
<h1>JSONP原理by 司徒正美</h1>
</body>
</html>
这个页面上有一个内部脚本,我称之为脚本片断1。它将在页面加载后,动态生成一个script标签,设置src并加入DOM树中。重点就是这个src的构成,它是一个普通url与一些查询参数组成的。这些查询参数视公司而定,但其中用一个肯定是用来标识回调函数的名字。我这里是jsonpCallBack,既然有函数名,必然要有函数本身。由于我们定义这函数时,不一定在全局作用域下,因此我为它加了个“window前缀”。有一个有趣的比喻说,全局作用域就像一个公共厕所,虽然你免不了要去它那里,但你也应该少去它那里。如果可能,我们在使用了它以后,也应该努力打扫一下它,避免全局变量过多,减少命名冲突的风险。不过,现在我们不打扫了,这还涉及一个问题。我们看后台是怎么处理的。
既然是这script标签添加src属性,它理应是一个JS文件,要不会报错,在标准浏览器下我们可以使用onerror来监听它。但现在我们怎么看它也不是一个JS文件。这就全然靠目标服务器的造化了!当我们把这段奇怪的东西发送过去时,它应该会把这些参数与函数名进行分解,根据参数生成一个JSON,写入一个JS文件(动态生成的),然后取得函数名,由于这是一个全局函数,可以直接调用。嘛,反正此时是文本状态,在函数名加一对括号,把JSON放到中间便是。换言之,这个JS文件是这个样子:
//动态加载的新JS文件
var json = {"title":"Recent Uploads tagged cat",
"link":"http://www.flickr.com/photos/tags/cat/",
"description":"",
"modified":"2010-05-25T17:21:19Z",
"generator":"http://www.flickr.com/",
"items":[/**很多很多的数据,这里略**/]}
window.jsonpCallBack(json)
因此,一旦这JS文件被解析,就能执行回调函数与处理我们请求回来的数据(JSON)。好了,是时候打扫一下了。首先这个用于后台交互的script标签,在回调函数执行完没有用了,我们可以移除它。另,回调函数执行完了,这个函数也没有用了,也可以移除了。
window.jsonpCallBack = function(json){
for(var item in json){
alert(item)
}
if(window.JSON){
alert(window.JSON.stringify(json))
}
script.parentNode && script.parentNode.removeChild( script );
//这是一种权宜之计,虽然值设为undefined了,但我们还能在for...in循环中遍历出jsonpCallBack
window.jsonpCallBack = undefined;
try {
//彻底删除jsonpCallBack这个成员
//只可惜IE的window是基于COM,没有delete这方法,因此失败!
alert(window instanceof Object)
delete window.jsonpCallBack
} catch(e) {}
}
注释中也说了,真是请神容易送神难。为了我们需要改变策略,采用单足独立式结构,减少全局污染!换言之,这些回调函数的方法名必须成为某个对象的成员!为了,提高效率,我搞了对象数组(如果只用一个对象来装载它们,for...in循环恶心死了!)
//小型JSONP类库 by 司徒正美
var dom = {};//暴露类库唯一一个全局变量作为命名空间
dom.jsonp = function(url,obj,method){
var self = arguments.callee;
if (self.callbacks.length) {//实现异步装载,异步回调
setTimeout(function() {self(url,obj,method)}, 0);
return;
}
var query =[];
for(var i in obj){
query.push(i +"="+obj[i])
}
var callback = "dom.jsonp.callback"
url = url+"?"+ query.join("&")+"&"+method+"="+callback;
var script = document.createElement("script");
script.src = url;
var head = document.getElementsByTagName("head")[0];
var obj = {
method:function(json){
for(var item in json){
alert(item)
}
if(window.JSON){
alert(window.JSON.stringify(json))
}
script.parentNode && script.parentNode.removeChild( script );
}
}
dom.jsonp.addCallback(obj)
head.appendChild(script);
}
dom.jsonp.callbacks = []
dom.jsonp.addCallback = function(obj){
this.callbacks.push(obj)
}
dom.jsonp.callback = function(json){//统一处理回调函数
var objs = this.callbacks;
for (var i=0,el;el=objs[i++];) {
el.method.call(el,json)
}
this.callbacks = [];
}
window.onload = function(){
dom.jsonp("http://api.flickr.com/services/feeds/photos_public.gne",{
tags: 'cat',
tagmode: 'any',
format: 'json' },"jsoncallback");
dom.jsonp("http://api.cnet.com/restApi/v1.0/techProductSearch",{
partTag: 'mtvo',
iod: 'hlPrice',
viewType: 'json',
results: '100',
query: 'ipod'},"callback");
dom.jsonp("http://del.icio.us/feeds/json/fans/stomita",null,"callback");
}
浙公网安备 33010602011771号