使用JSONP来取代AJAX进行跨域

标签:JavaScript, jQuery

说到用JavaScript动态加载内容,一般都会想到AJAX。
但AJAX所用的XML其实并非必须,还有其他类型的数据结构(例如JSON和HTML)也可以实现。
而且AJAX有个很大的问题:由于JavaScript的安全限制,AJAX的跨域非常麻烦。
但如果使用JSONP的话,问题就很容易搞定了。

JSONP就是JSON with Padding的缩写,意思是JSON加上一些填充。
那么究竟填充什么呢?这就要说下原理了。
当使用script标签时,浏览器可以加载来自外域的JavaScript文件。而JSON本身就是一个JavaScript的对象,所以浏览器也可以直接加载它们。
但单纯的加载毫无用处,因为JSON只是数据,我们还需要处理它。因此,假如用括号将JSON数据包围起来,然后传给一个JavaScript function,那这个function就能处理这个JSON数据了。
因此从技术上来说,JSONP并不是数据,而是JavaScript代码,和JSON是2个稍有区别的东西。

首先来看最简单的实现:
客户端:
<html>
 <head>
  <title>JSONP Test</title>
	<script>
		function test(text) {alert(text.hello);}
	</script>
	<script src="test.json"></script>
 </head>
 <body>
 </body>
</html>
服务器端(就是那个test.json)
test({"hello": "hello world!"})
在浏览器中打开这个页面,将会弹出一个对话框,内容是hello world!
事实上,我们的test.json文件就是个JavaScript文件,它调用test方法,并将其中的JSON数据({"hello": "hello world!"})作为参数。

也就是说,JSONP就是一次函数调用而已,明白这点就好办了。
不过动态加载当然不可能直接用script标签来做,但JavaScript是可以操作DOM的,所以我们只需要把script标签添加到DOM,就能实现动态加载了。

当然,改写DOM是个很麻烦的过程,我们可以使用一些JavaScript库来实现,下面就给个jQuery和GAE的例子:
客户端:
<!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=utf-8" />
  <meta name="author" content="keakon" /> 
  <title>JSONP Test</title>
	<script type="text/javascript" src="http://www.google.com/jsapi"></script>
	<script type="text/javascript">
		google.load("jquery", "1.3.1");
	</script>
 </head>

 <body>
 <script>
	$.getJSON("http://localhost:8080/?jsoncallback=?",
		function (json) {
			alert(json.text);
		}
	);
 </script>
 </body>
</html>
服务器端(本来是发在Google的论坛的,所以注释用的是英文,将就看吧):
import wsgiref.handlers
from google.appengine.ext import webapp

class MainHandler(webapp.RequestHandler):
  def get(self):

    # Since browsers can't handle application/json as a text,
    # and actually, it's a JavaScript code, not JSON, while I request jsoncallback,
    # I use text/javascript to both display and process it.
    self.response.headers['Content-Type'] = 'text/javascript; charset=UTF-8'

    json = {"text": "Hello world!"} # the json data I want to output
    jsoncallback = self.request.get('jsoncallback') # the jsoncallback argument

    # if doesn't contain jsoncallback argument, simply output it
    # otherwise, it should output jsoncallback_argument(json data)
    # eg: hello({"text": "Hello world!"})
    output = '%s(%s)' % (jsoncallback, json) if jsoncallback else json

    self.response.out.write(output)

def main():
  application = webapp.WSGIApplication([('/', MainHandler)])
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
  main()
jQuery隐藏了很多实现,实际上它会自动判断jsoncallback=?,然后将?替换为一个随机生成的函数名(实际上就是我们的匿名函数),于是就形成了一次函数调用。

注意jsoncallback不是必须的,只是jQuery需要它的名字类似callback
而且大多数都遵循这个命名规则,所以JSONP也被叫作JSON with callbacks
个人也比较喜欢后者,因为更加贴近其原理,而不是其表面现象(填充了函数名和括号的JSON)。
当然,你可以更改这个函数名,但必须使用$.AJAX函数的jsonp参数来指定,$.getJSON函数是不提供这个参数的。

还在为AJAX跨域苦恼的不妨试试JSONP吧,而且JSON本身比XML也方便很多。

0条评论 你不来一发么↓

    想说点什么呢?