脚本绑定回调:不可能完成的任务

来源:互联网 发布:傅园慧 知乎 编辑:程序博客网 时间:2024/04/29 20:45

转自EMU前辈的BLOG:http://www.blogjava.net/emu/articles/129240.html

如果不用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在这个问题上花费过无数心血后最终还是放弃了,直到昨晚,舜子才终于有了突破:

...省略若干...

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


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>转:EMU前辈的http://www.blogjava.net/emu/articles/129240.html</title>
<script type="text/javascript">

//那文件返回的内容是类似这样的
//visitCountCallBack({"visitcount":65401864,"dayvisit":12571,"spacemark":0,"markchange":0,"sun":1728,"love":478,"rain":1728,"nutri":1450,"level":5,"gardener":1});

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("<sc" + "ript src=/"" + url + "/"><//sc" + "ript><sc" + "ript defer>setTimeout(/"errcallback()/", 0)<//sc" + "ript>");
            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:/"<sc" + "ript>function visitCountCallBack(data) { frameElement.callback(data) };<//sc" + "ript>"
            + "<sc" + "ript src='" + url + "'><//sc" + "ript><sc" + "ript>setTimeout('frameElement.errcallback()', 0)<//sc" + "ript>/"";
        document.body.appendChild(i);
    }
}

window.onload = function () {
    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>
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(应该是指小沙漏型的下载图标吧?)。针对具体的用户群的浏览器种类,上面几种方案不用全上,看需要了。