有关豆瓣 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 客户端
好文,学习了
cb1是什么函数?
cb1 是我测试用的假想函数名。
那应该是rm1才对啊
。。。