#0x01 写作背景
第三期极客大挑战编程题里有一个修改User-Agent的题目,所以就想了想能不能用js来实现,但是出了很多问题,使得我回头阅读《JavaScript权威指南》,有了很多心的收获,特别是关于JSONP,所以就有了这篇博文。
#0x02 关于__同源策略__
同源策略是对JavaScript代码能够操作哪些Web内容的一条完整的安全策略。当Web页面使用多个<iframe>
元素或者打开其他页面窗口的时候,这一策略通常就会发挥作用。在这种情况下,同源策略负责管理窗口或者窗体中的JavaScript代码以及和其他窗口或帧的交互。具体来说,脚本只能读取和所属文档来源相同的窗口和文档属性,而源包括协议、域名或者主机号、端口号。
#0x03 关于跨域AJAX请求
发起AJAX请求,我们通常使用XMLHttpRequest对象,但是它作为同源策略的一部分,通常只能够发起和文档具有相同服务器的HTTP请求。
而<script>
元素的src属性能设置URL并发起HTTP GET请求。使用<script>
元素实现脚本操作HTTP是很吸引人的,因为他们可以实现跨域通讯而不受限于同源策略,并且他可以直接对JSON编码的数据格式进行解码。
如果这种使用<script>
元素作为AJAX传输的技术,并且通过HTTP请求的到的数据是__按照一定规则构造后__的JSON,这个技术就是传说中的__JSONP__(JavaScript Object Notation with Padding)。(先不要纠结这一句,继续往下)
#0x04 JSONP详解
我们先来看这样段示例代码:
<!doctype html>
<html>
<head>
<title>Just A Demo</title>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
function showMessage(message){
alert(message);
}
</script>
<script type="text/javascript" src="http://xxx.xxx/data"></script>
</body>
</html>
data文件的内容是:
showMessage('我来自火星!')
运行这段代码,会出现弹窗“我来自火星”。完毫无疑问,跨域调用成功了,我们成功传递了‘我来自火星’这个字符串。
但是这个跟JSON似乎不沾边呀!
现在对上面的示例代码进行一个小小的改动:
<!doctype html>
<html>
<head>
<title>Just A Demo</title>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
function showMessage(data){
alert(data.name);
alert(data.description);
}
</script>
<script type="text/javascript" src="http://xxx.xxx/data"></script>
</body>
</html>
data文件的内容还是:
showMessage({'name': 'linkgod', 'description': 'A OK Man!'})
通过这个例子,我们不难理解JSONP中的P,“Padding:v.给…装衬垫,加垫子(pad的现在分词)”,使用括号()
将JSON数据给装垫起来。
通过上个部分我们看到了JSONP的本质是使用<script>
元素调用数据,最后响应的内容自动被构造成JavaScript代码。
AJAX(Asynchronous JavaScript and XML)和JSONP在本质上是不同的,AJAX的核心是通过XmlHttpRequest对象进行HTTP请求,而JSONP的是通过<script>
标签的src来调用服务器提供的js脚本。
在了解清楚JSONP的原理之后,我们来写一个getJSONP函数,总不能每次都写一个<script>
标签吧!
<!doctype html>
<html>
<head>
<title>Just A Demo</title>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
//定义getJSONP函数
function getJSONP (url, callback) {
showMessage = function(response){
try{
callback(response);
}finally{
delete showMessage;
script.parentNode.removeChild(script);
}
}
var script = document.createElement("script");
script.src = url;
document.body.appendChild(script);
}
//执行JSONP请求
getJSONP('http://xxx.xxx/data', function(data){
alert(data.name);
alert(data.description);
})
</script>
</body>
</html>
data文件的内容仍然是:
showMessage({'name': 'linkgod', 'description': 'A OK Man!'})
但是每一次返回的JSONP前面还有一个方法名,我们必须跟写WebServer的工程师约定好方法名,或者就想办法告诉WebServer方法的名字。
这个可以通过在URL后面添加一个参数实现:例如“?jsonp”
//代码来源于《JavaScript权威指南》
function getJSONP (url, callback) {
//为本次请求创建一个唯一的回调函数名
var cbnum = "cb" + getJSONP.couter++; //每次自增计数器
var cbname = "getJSONP." + cbnum; //作为JSONP函数的属性
if(url.indexOf('?') === -1)
{
url += "?jsonp=" + cbname;
}else{
url += "&jsonp=" +cbname;
}
var script = document.createElement("script");
//定义将会被脚本执行的回调函数
getJSONP[cbnum] = function(response){
try{
callback(response);
}finally{
delete getJSONP[cbnum];
script.parentNode.removeChild(script);
}
}
//触发HTTP请求
script.src = url;
document.body.appendChild(script);
}
getJSONP.couter = 0; //用于创建唯一回调函数名称的计数器