Ruby's Louvre

每天学习一点点算法

导航

跨域的异步请求二

说一下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");
      }

//http://www.blogjava.net/emu/articles/129240.html var dom = {};//暴露类库唯一一个全局变量作为命名空间 //{url,obj,callbackName,callback} dom.jsonp = function(url,obj,method,callback,caching){ method = method || "callback"; var script = document.createElement('script'); var index = dom.jsonp.index++; var query = dom.toQueryString(obj); callback = callback || function(json){ for(var item in json){ alert(item) } if(window.JSON){ alert(window.JSON.stringify(json)) } } url = url+"?"+ query+"&"+method+"="+escape('dom.jsonp.callbacks._'+index); if (!caching) url += '&rand='+Math.random(); dom.jsonp.callbacks['_'+index] = function() { delete dom.jsonp.callbacks['_'+index]; if (arguments.length==1) { callback(arguments[0]); } else { var arry = []; for (var i=0; i

posted on 2010-05-26 10:39  司徒正美  阅读(2765)  评论(2编辑  收藏  举报