跨域的异步请求二
说一下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" ); } |
posted on 2010-05-26 10:39 司徒正美 阅读(2794) 评论(2) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?