有关豆瓣 API 的 javascript 客户端

2010年9月7日
有关豆瓣 API 的 javascript 客户端

Robbie Mosaic (范德蛙)

douban.com 豆瓣在 2009 年(可能更早)引入了豆瓣 API,并提供 HTML/javascript 客户端。它的实现方式是怎样的呢?出于好奇,范德蛙在此作了一个简单的研究。声明在先:本文仅限豆瓣开发爱好者交流,绝不可用于商业用途。

首先,要使用此 javascript 客户端,把以下地址的 js 包含到你的网页中:

<script type="text/javascript" src="http://www.douban.com/js/api.js?v=2" />

然后就可以调用豆瓣 API 了:

DOUBAN.apikey = ‘c4579586f41a90372f762cb65c78be5d’

DOUBAN.getMovie({
    id:’2340927′,
    callback:function(movie){
        var title = movie.title[‘$t’];
                …
    }
})

那么它是如何实现的呢?前述 javascript 包含某些代码,比如会把 DOUBAN 加为 window 对象的属性等等。本文关注它如何从网页上获取数据。首先,豆瓣包含许多直接返回 XML 的 API。比如,打开页面:

http://api.douban.com/people?q=%e8%8c%83%e5%be%b7%e8%9b%99

会得到一个 XML,里面包含搜索“范德蛙”的结果。我好奇的是,javascript 是如何将这些数据获取的。用 js 获取 XML,我能想到的第一种方式就是使用 XMLHttpRequest 对象。但是,据我所知,使用此对象时,Firefox/Seamonkey 会对它的目标 url 与当前页面的 url 进行域名检查,并当域名不一致时禁止获取 XML 的请求。因此豆瓣的实现方式一定比这个有趣。

仔细阅读了它的源代码,首先 apis 是一个对象,它有许多属性,每个属性有一个 url,对应一个 API。

    var baseUri = ‘http://api.douban.com/’;
    …
    var sp = ‘q={keyword}&’+pp; // +pp 参数可以省略
    …
    var apis = {
        getUser: {url:baseUri+’people/{id}’},
        searchUsers: {url:baseUri+’people?’+sp},
        …
    };

从上面的 apis.searchUsers 来看,如果直接打开 http://api.douban.com/people?q=%e8%8c%83%e5%be%b7%e8%9b%99 这个页面,得到的仍旧是 XML。我发现它随后有个循环,用于绑定方法名到 api_obj 这个对象上去:

    for (var name in apis)
        api_obj[name] = (function(url){
            return function(params){
                send(url, formatParams(params));
            };
        })(apis[name].url)

其中关键的一个函数是 send,而 send 则进一步调用 sendScriptRequest 函数。

    var sendScriptRequest = function(url){
        var head = document.getElementsByTagName("head")[0];
        var script = document.createElement("script");
        script.src = url;
        script.charset = ‘utf-8’;
        head.appendChild(script);
    }

可见 sendScriptRequest 它起的作用是在 HTML 的 head 标签下加一个子标签 script,然后新增一个 javascript 进来。然而,如果请求得到的是 XML,那怎么能当 javascript 使用呢?果然,我发现事实不是这样,前面我略过了一个循环:

    var cp = ‘apikey={apikey}&alt=xd&callback={callback}’;
    …
    for (var name in apis)
        if (apis[name].url.search(/?/)!=-1) apis[name].url = apis[name].url + ‘&’ + cp;
        else apis[name].url = apis[name].url + ‘?’ + cp;

以上循环做的是把 url 根据它是否带有 ‘?’ 以不同方式追加 cp 这个字符串。如果我把 cp 中关键的那部分加上试试:

http://api.douban.com/people?q=%e8%8c%83%e5%be%b7%e8%9b%99&alt=xd&callback=cb1

啊,这下出来了,是一段 js:

cb1({"openSearch:totalResults":{"$t":"1"},"openSearch:startIndex": {"$t":"1"},"openSearch:itemsPerPage":{"$t":"10"},"entry":[{"db:uid": {"$t":"2479191"},"db:signature":{"$t":""},"title":{"$t":"范德蛙"},"uri": [{"$t":"http://api.douban.com/people/2479191"}],"content": {"$t":""},"link":[{"@rel":"self","@href":"http://api.douban.com /people/2479191"},{"@rel":"alternate","@href":"http://www.douban.com /people/2479191/"},{"@rel":"icon","@href":"http://img3.douban.com /icon/user.jpg"}],"id":{"$t":"http://api.douban.com/people /2479191"}}],"title":{"$t":"搜索 范德蛙 的结果"}})

而且是经过 json 编码的数据。其中 cb1 是我试验用的回调函数的名字。这样一来,就会在 head 中增加这么一段 js,然后这段 js 会调用 cb1 这个回调函数,回调函数就会接收到这个 json 对象。大功告成了。

问题:head 中的 script 会不会越加越多造成内存泄漏?

参考资料:
[1] http://www.douban.com/service/apidoc/guide – 豆瓣 API 快速入门
[2] http://www.douban.com/service/apidoc/clients – 豆瓣 API 客户端

“有关豆瓣 API 的 javascript 客户端” 已有 5 条评论

  1. Tony 在

    好文,学习了

  2. 博 在

    cb1是什么函数?

  3. Decheng 在

    cb1 是我测试用的假想函数名。

  4. 博 在

    那应该是rm1才对啊

  5. Decheng 在

    。。。

留下您的评论