emu in blogjava

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  171 随笔 :: 103 文章 :: 1052 评论 :: 2 Trackbacks
如果不用xmlhttp方式获取json数据,一般我们最好用的方式是用script标签直接引用需要的脚本。但是不像xmlhttp可以很容易的把请求数据脚本和请求到的数据绑定到一起,script标签本身是无法获知自己获得了什么数据的,这个问题上一般使用的解决方案有:

1 事先约定前后台接口。这样带来了很强的前后台偶合,后台程序需要知道前台想要做什么,接口很难一致化,一般不同的服务程序要使用不同的接口。而且如果需要同时并发调用同一个服务程序几次,那么一样无法解决接口冲突问题。

2 前台动态生成回调接口后把接口名称传递给后台程序,后台程序根据接受到的接口名称动态生成回调接口,比如google就喜欢接受callback参数: http://www.google.com/reader/public/javascript/user/10949413115399023739/label/officialgoogleblogs?n=10&callback=test
饭否的接口也是这样的:
http://api.fanfou.com/statuses/user_timeline.json?callback=test
这样也是一个无奈之举,一样避免不了的令人生厌的前后台偶合,只是改变了偶合的方式,前后台需要换一种方式的约定,而且如果要解决并行多个异步回调的接口冲突问题,就要动态的给每个回调函数创建一个个不同的名称,此外服务程序的输出不允许静态化,必须有接受参数和生成回调脚本的功能。

假如我们想要像生成静态rss(http://api.fanfou.com/statuses/user_timeline.rss)文件一样的生成静态的json(http://api.fanfou.com/statuses/user_timeline.json)又不希望或者不能使用xmlhttp来拉取json字符串,而想要用一致的callback接口来回传数据,那么怎么样才能解决接口冲突问题呢?事实上只有做到这点,json才能真正想xml一样变成一个纯粹的数据描述方式,摆脱对具体上下文程序的依赖,让一个数据自由的被不同目的的页面mashup。比如说,在一个页面上用json结合脚本技术,把来自不同网站的相同格式的json数据合并显示到一个页面上。

emu在这个问题上花费过无数心血后最终还是放弃了,直到昨晚,舜子才终于有了突破:

<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
function loadjs(url,callback){
    
if(window.ActiveXObject){
        
var df = document.createDocumentFragment();    
        df.visitCountCallBack 
= callback
        
var s = document.createElement("script");
        df.appendChild(s)
        s.src
=url;
    }
else{
        
var i = document.createElement("IFRAME");    
        i.callbackID 
= "2";
        i.style.display
="none";
        i.callback
=callback;
        i.src
="javascript:\"<script>function visitCountCallBack(o){frameElement.callback(o)}<\/script><script src='"+url+"'><\/script>\""
        document.body.appendChild(i);
        i.contentWindow.callback 
= callback
    }
}

function init(){
    
var spans = document.getElementsByTagName("span");
    
for(var i=0;i<spans.length;i++){
        
var id = spans[i].id;
        
var url = "http://g2.qzone.qq.com/fcg-bin/cgi_emotion_list.fcg?uin="+id;
        
var callback = function(id){ return function(data){
            document.getElementById(id).innerHTML 
= data.visitcount;
            }
        }(id);
        loadjs(url,callback);
    }
}
</SCRIPT>
</HEAD>
<BODY onload="init()">
123456 的访问量:
<span id="123456"></span><BR>
2543061 的访问量:
<span id="2543061"></span><BR>
20050606 的访问量:
<span id="20050606"></span><BR>
</BODY>
</HTML>


如果需要支持错误处理,就稍微麻烦一点了,emu的做法是这样的: 

 

 

<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
var isIE = !!window.ActiveXObject;
var useFragment=false;
function loadjs(url,callback,errcallback){
    
if(isIE){
        
if(useFragment){
           
var df = document.createDocumentFragment();    
            df.visitCountCallBack 
= function(data){
                s.onreadystatechange
=null;
                df
=null;
                callback(data);
            }
            
var s = df.createElement("SCRIPT");
            df.appendChild(s);
            s.onreadystatechange
=function(){
                
if(s.readyState=="loaded") {
                    s.onreadystatechange
=null;
                    df
=null;
                    errcallback();
                }
            }
            s.src 
= url;
        }
else{
            
var i=new ActiveXObject("htmlfile");
            i.open();
            i.parentWindow.visitCountCallBack
=function(i){
                
return function(d){
                    i.parentWindow.errcallback
=null;
                    i
=null;
                    callback(d);
                }
            }(i);
            i.parentWindow.errcallback
=function(d){
                i.parentWindow.errcallback
=null;
                i
=null;
                errcallback(d);
            }
            i.write(
"<script src=\""+url+"\"><\/script><script defer>setTimeout(\"errcallback()\",0)<\/script>")
            
if(i)i.close();//如果数据被cache,运行到这一行的时候有可能回调已经完成,窗口已经关闭。
        }
    }
else{
        
var i = document.createElement("IFRAME");    
        i.style.display
="none";
        i.callback
=function(o){
            callback(o);
            i.contentWindow.callback
=null;
            i.src
="about:blank"
            i.parentNode.removeChild(i);
            i 
= null;
        };
        i.errcallback 
= errcallback;
        i.src
="javascript:\"<script>function visitCountCallBack(data){frameElement.callback(data)};<\/script><script src='"+url+"'><\/script><script>setTimeout('frameElement.errcallback()',0)<\/script>\"";
        document.body.appendChild(i);
    }
}

function init(){
    
var spans = document.getElementsByTagName("span");
    
for(var i=0;i<spans.length;i++){
        
var id = spans[i].id;
        
var url = "http://g2.qzone.qq.com/fcg-bin/cgi_emotion_list.fcg?uin="+id;
        
var callback = function(id){ return function(data){
            document.getElementById(id).innerHTML 
= data.visitcount;
            }
        }(id);
        
var errcallback = function(id){ return function(){
            document.getElementById(id).innerHTML 
= "无法连接到服务器";
            }
        }(id);
        loadjs(url,callback,errcallback);
    }
}
</SCRIPT>
</HEAD>
<BODY onload="init()">
123456 的访问量:
<span id="123456"></span><BR>
2543061 的访问量:
<span id="2543061"></span><BR>
20050606 的访问量:
<span id="20050606"></span><BR>
</BODY>
</HTML>



在IE/FIREFOX/OPERA/SAFARI上运行通过。

这里有几点说明:IE其实也可以用iframe(试试强行给isIE变量赋false值),不过用iframe的缺点是phantom click(会发出一个页面跳转的小声音)和throbber of doom(应该是指小沙漏型的下载图标吧?)。

用document fragment的好处是避免了IE7默认安全模式下面禁止ActiveX的问题。不过利用了IE的一个特点:document fragment不append到document的dom里面的时候,也可以拥有自己的脚本运行空间,可以用script标签发起请求。这样用document fragment就可以比iframe使用更少的客户端资源来完成操作。

虽然多个版本的IE都支持这个特性,但是emu还是认为其他非IE浏览器的处理更为合理,为了防止将来万一IE fix了这个bug造成措手不及,emu准备了另外两个备用方案,一个是当useFragment被声明为false的情况下,可以用一个htmlfile的控件来代替(google在gmail中使用了这个控件,但是造成一些用户在抱怨IE7下面的安全提示);另一个是如果不能用ActiveX,还可以走非IE浏览器的逻辑,用iframe来完成操作,但是耗费的客户端资源要稍微多一点。用iframe另外两个的缺点是phantom click(会发出一个页面跳转的小声音)和throbber of doom(应该是指小沙漏型的下载图标吧?)。针对具体的用户群的浏览器种类,上面几种方案不用全上,看需要了。

firefox下面的script标签其实支持onerror事件(可以写在标签里面或者addEventListener上去),其他浏览器根据版本的不同对此有不同程度的支持,所以emu决定利用script标签可以堵塞页面运行过程的做法,script标签后面添加延迟的错误处理逻辑(在正确的情形下抢先清空掉iframe的内容来取消这个逻辑)。

posted on 2007-07-10 10:07 emu 阅读(3108) 评论(11)  编辑  收藏 所属分类: DHTML和JAVASCRIPT 技术

评论

# re: 脚本绑定回调:不可能完成的任务 2007-07-18 22:22 emu
写了个页面测试三种不同的创建document的方式的时间耗费:

<HTML>
<HEAD>
</HEAD>
<BODY>
document fragment:<span id="t1"></span><br>
htmlfile:<span id="t2"></span><br>
iframe:<span id="t3"></span><br>
<button onclick="test()">test</button>
<SCRIPT LANGUAGE="JavaScript">
var n = 100;
function test(){
var t=new Date();
for(var i=0;i<n;i++)
document.createDocumentFragment()
document.getElementById("t1").innerHTML=(new Date()-t);
if(window.ActiveXObject){
var t=new Date();
for(var i=0;i<n;i++)
new ActiveXObject("htmlfile")
document.getElementById("t2").innerHTML=(new Date()-t);
}
var t=new Date();
for(var i=0;i<n;i++){
var tt = document.createElement("IFRAME");
tt.style.display="none";
document.body.appendChild(tt);
}
document.getElementById("t3").innerHTML=(new Date()-t);
}
</SCRIPT>
</BODY>
</HTML>

在ie上基本上是个1:10:100的比例。在firefox上iframe的速度和IE差不多,documentfragment的速度快一些,但是不能创建变量空间,没有什么意义。safari创建documentfragment更快,可是创建iframe更慢了。最惨的是opera,居然每次插入不可见的iframe都会导致页面重新渲染,最终浏览器都挂掉了。  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2008-03-11 19:45 hax
emu同志,您的这个方法其实还是只是对最终编程者隐藏了callback的名字而已,并没有提供多少额外的好处也。callback的名字(比如您的visitCountCallback)归根到底是省不掉的,只是是否暴露给最终编程者的差别。

因此您说的“事实上只有做到这点,json才能真正想xml一样变成一个纯粹的数据描述方式,摆脱对具体上下文程序的依赖,让一个数据自由的被不同目的的页面mashup。比如说,在一个页面上用json结合脚本技术,把来自不同网站的相同格式的json数据合并显示到一个页面上。”其实也站不住脚。或者说,要达到这个目标的前提是,都采用你的这个方案。问题是,如果可以都采用一种方案,那怎么样都行,不是吗?

总之,只是一个callback包装——如果要包装的话,不用iframe这样的技巧,其实也可以办到。只要用你包装好的回调,没有什么是办不到的。  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2008-03-12 01:39 emu
嗯,我想差别还是蛮大的。
1 callback的名字如果暴露给最终编程者,首先如果有多个一模一样的callback(但是其中的数据不同),难免就要相互冲突。
2 你所说的前提如果是“用callback方式回传数据”的话,其实这个方式现在是最流行的方式了。
3 我的方案本来也不依赖iframe。只是不用iframe的话只能在IE上面用而已。  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2008-03-13 10:20 hax
我的意思是,无论你用iframe或是只能用于ie的documentFragment(这明显是ie的一个问题,凭什么要为任意一个新节点产生一个新的脚本scope?而且我怀疑这会引起内存泄漏——ms的人在说memory leak的patterns那个文章里曾经提到过由这一问题引起的内存泄漏),所付出的代价似乎比不上得到的好处。

因为不用iframe,你也可以达到向最终编程者隐藏callback名字的目标,只需要callback是由你的框架托管和包装起来就可以了。差别只在于,这种方式,这个callback名字会污染当前的global上的命名空间,iframe则不会,因为它在一个独立的scope里——但是付出的代价是有独立的browserContext(包括window、document等对象)。  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2008-03-13 10:36 hax
因此,iframe只是一个锦上添花的工具。我认为理想的目标是:

1. callback名字最好是可配置的。因为万一有些人写死了callback名字。。。
2. 有些人不仅写死了,而且不是callback({...json...}),而是var hardCodeJSON = {...json...},最好也能处理这种情况(肯定是可以做到的)。
3. 不能每次都搞一个iframe,这样太浪费。iframe的唯一目的其实是搞一个独立的scope,所以弄一个iframe就可以了。还有基于前面提到的因素,建议不要用documentFragment。

那么在一个iframe情况下,你说的“多个一模一样的callback”的冲突还是存在。这就需要用其他技巧解决。所以反过来,如果其他技巧解决了这个问题,那么iframe的唯一好处就是一丁点都不污染全局命名空间——个人来讲,我认为这个好处还是相当有限的。

下面说一个一般化(也就是不需要iframe)的最土的思路。原本我们插入一个<script>标签来读取json。现在我们可以插入3个<script>。第一个做初始化,建立服务器所需要的callback函数,如果有命名冲突,则把之前的那个变量保存起来。第二个还是load JSON。第三个做清理,去掉服务器所用的callback,并把之前的变量恢复起来。

当然这只是一个一般的描述,实际做的时候,肯定有很多问题要解决,但是从大体上看,应该是行得通的。

  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2008-03-13 14:38 emu
呵呵,假如操作是串行的是行得通的,但是串行的操作本来也很容易处理命名冲突。如果操作是异步化的,并行发起的请求,并且请求的发起顺序和相应顺序是没有得到任何保证的,那么你说的“如果有命名冲突,则把之前的那个变量保存起来”能解决得了什么问题呢?
至于内存泄漏问题,一方面,内存泄漏要靠工具检查确认是否存在,而不是靠猜它可能存在就简单的去放弃一个做法的。二方面,就算发现了内存泄漏,应该想办法定位到内存泄漏的原因并解决它。最后,即使真的一时没有办法解决内存泄漏,带来的影响还是需要评估的?比如有的时候定时器调用函数会造成32byte的内存泄漏,能解决当然很好,不解决会有很大问题吗?IE一关掉内存就收回来了,用户又不会一直开着IE三个月不关。  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2008-03-13 18:04 我佛山人
我觉得这种方式不错,而且我改良过之后,根本不需要建立名为visitCountCallBack的函数,只需要约定var json = {...};

演示:http://wfsr.net/temp/loadjson.html

另外我测试发现,firefox不支持相对路径,safari不支持javascript:这种方法写入iframe,需要write的  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2009-04-24 13:25 boyszz
简单事情复杂化的典型表现。  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务[未登录] 2009-05-20 01:14 小风
后台直接吐出一个json变量似乎也可以解决:

<?php
echo
' var JSON={ ...}; ' ;
?>

然后不是就可以像 jquery 请求返回一个 json 对象一样么?
$.post( url, {}, function(json){
json ....
} ,'json' );

好像上面也有和我想的一样的  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2009-05-22 11:39 emu
IE8beta版本不支持documentFragment拥有独立的运行空间,正式版又解决了这个问题,因此写了个小判断:

var df = document.createDocumentFragment();
df.callback=function(){alert("documentFragment有独立的脚本运行空间")};
window.callback=function(){alert("不支持documentFragment独立脚本运行空间")}
var s = document.createElement("script");
df.appendChild(s)
s.src="javascript:'callback()'";
  回复  更多评论
  

# re: 脚本绑定回调:不可能完成的任务 2010-06-24 11:47 adv
js + as3也可解决此种问题,缺点是浏览器需要播放器支持。
1。先从as3注册出一个方法供js调用,作用就是将请求的url转入到as中,通过as转发请求。
3。定义js函数供as调用,来结束请求的返回结果和对应的url地址
3。扩展as的URLLoader增加一个url属性来标识发生请求的url。
4。将结果和对应的url再次传入js方法。

如此,可以将并发请求的返回进行对应关系(并非对应顺序)。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: