1、什么是跨域
获取一个页面的域:
document.domain;
// qianduanblog.com
为了页面和服务器的安全(?),脚本是不能访问非本域的动态网络资源,但可以访问如脚本、样式、图片、视频、音频等这些静态资源。
那什么是跨域呢?存在以下情况中之一,即发生跨域:
-
网络协议不同,如
http
协议访问https
协议。 - 端口不同,如
80
端口访问8080
端口。 -
域名不同,如
qianduanblog.com
访问baidu.com
。 -
子域名不同,如
abc.qianduanblog.com
访问def.qianduanblog.com
。
跨域请求,如获取百度首页的内容:
// 本域:qianduanblog.com
// 他域:www.baidu.com
$.ajax("http://www.baidu.com");
// 报错:XMLHttpRequest cannot load http://www.baidu.com/. Origin http://qianduanblog.com is not allowed by Access-Control-Allow-Origin.
2、前端跨域原理
既然无法使用传统的XMLHttpRequest(即AJAX)实现跨域获取内容,那么是否还有其他办法呢?已经知道,静态资源是没有跨域限制的,那么是否可以通过请求静态资源的方法来实现跨域呢?
答案是肯定的。
通常,在前端开发中,实现跨域都是把动态资源伪装成脚本来实现跨域。如:
// 请求 http://qianduanblog.duapp.com/test/index.php?jsonp=testjsonp
// 返回 testjsonp({"time":"2013-11-20 13:46:30"});
可以在页面中写上:
<script>
function testjsonp() {
console.log(arguments[0]);
}
</script>
<script src="http://qianduanblog.duapp.com/test/index.php?jsonp=testjsonp"></script>
以上就是最原始的利用JSONP实现前端跨域。会在控制台输出:
Object {time: "2013-11-20 13:49:21"}
什么是JSONP,接下来会说到。
既然通过脚本可以读取他域上的动态资源,那么我们就可以动态创建script来读取他域的动态资源。
<script>
var url = "http://qianduanblog.duapp.com/test/index.php?jsonp=testjsonp";
var script = document.createElement("script");
var head = document.getElementsByTagName("head")[0];
script.src = url;
function testjsonp() {
console.log(arguments[0]);
}
script.onload = function() {
}
// 页面上插入该脚本
head.appendChild(script);
</script>
同样的道理,同样的结果,也会在页面输出:
Object {time: "2013-11-20 13:55:45"}
3、前端跨域方法
3.1、JSONP
如上,已经简单的说明了,如何使用JSONP实现前端跨域,现在来仔细说说。
先了解,什么是JSON。
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。
在做前后端交互的时候,JSON是一个利器,在AJAX运用中愈加流行,学习掌握JSON是前端开发的必经之路。
再了解下,什么是JSONP。
简易说明,JSONP就是包装的JSON,把P理解为package更为合适。例JSON和JSONP:
// JSON
{
"s": "b"
};
// JSONP
jsonp({
"s": "b"
});
如例,JSONP中JSON是jsonp方法的实参,这样写的目的是,打开这个JSONP就运行了jsonp方法。如我们单独请求该脚本地址:
<script src="http://qianduanblog.duapp.com/test/index.php?jsonp=jsonp"></script>
<!-- 报错:Uncaught ReferenceError: jsonp is not defined -->
出现预想的错误,jsonp方法未定义,证明了以上说的是正确的。所以在处理JSONP的时候,我们需要预先定义一个全局函数jsonp,然后在该函数中返回实参即可。即:
<script>
// 预先顶一个全局函数 jsonp
function jsonp() {
// 输出该函数的实参
console.log(arguments[0]);
}
</script>
<script src="http://qianduanblog.duapp.com/test/index.php?jsonp=jsonp"></script>
所以结合动态创建script标签,可以这样写:
function getTime(callback) {
var url = "http://qianduanblog.duapp.com/test/index.php?jsonp=testjsonp";
var script = document.createElement("script");
var head = document.getElementsByTagName("head")[0];
script.src = url;
window.testjsonp = function() {
callback(arguments[0]);
}
script.onload = function() {
// 移除该script
script.parentNode.removeChild(script);
// 删除该script
script = null;
// 删除方法
window.testjsonp = null;
}
// 页面上插入该脚本
head.appendChild(script);
}
// 跨域获取时间
getTime(function(json) {
alert(json.time);
});
3.2、VAR
JSONP是利用全局方法来实现跨域读取,当然也可以利用全局变量来实现跨域读取。例:
var window.testvar = "123";
alert(window.testvar);
并且这个方法比JSONP要来的更加简单一点,具体实现方法例:
function getTime(callback) {
var url = "http://qianduanblog.duapp.com/test/index.php?var=testvar";
var script = document.createElement("script");
var head = document.getElementsByTagName("head")[0];
script.src = url;
script.onload = function() {
// 回调
callback(testvar);
// 移除该script
script.parentNode.removeChild(script);
// 删除该script
script = null;
// 删除变量
window.testvar = null;
}
// 页面上插入该脚本
head.appendChild(script);
}
getTime(function(json) {
alert(json.time);
});
3.3、修缮与扩展
在跨域读取动态内容,无论是利用JSONP还是VAR方法,都需要面对覆盖全局方法、全局变量的危险,解决这样的情况,我们可以生成一个唯一的函数名或者变量名来尽可能的防止出现这样的情况,例:
functionName = "yundanran" + new Date().getTime();
varName = "yundanran" + new Date().getTime();
这样的重复的概率就大大降低了。
第二个问题是,如何扩展该方法,两种方法大都雷同,可以合二为一。综合例:
/**
* 跨域读取
* @param {String} 跨域方法,jsonp或var
* @param {String} 跨域地址
* @param {Function} 跨域成功回调
* @param {Function} 跨域失败回调
* @return {Undefined} 无返回
* @author 云淡然
* @version 1.0
* 2013年11月20日14:30:51
*/
function crossDomain(type, url, onsuccess, onerror) {
// 设置回调为
var callbackName = "prefix" + new Date().getTime() + "callback";
// 创建回调函数
if (type == "jsonp") {
window[callbackName] = function () {
if (onsuccess) onsuccess(arguments[0]);
}
}
// 创建一个 script 的 DOM 对象
script = document.createElement("script");
// 设置其同步属性
script.async = true;
// 设置其地址
script.src = url.replace(/#.*$/, "") + (/\?/.test(url) ? "&" : "?") + type + "=" + callbackName;
// 监听
script.onload = script.onreadystatechange = function () {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = script.onreadystatechange = null;
if (type == "var") {
if (onsuccess) onsuccess(window[callbackName]);
}
// 移除该 script 的 DOM 对象
if (script.parentNode) {
script.parentNode.removeChild(script);
}
// 删除函数或变量
window[callbackName] = null;
}
}
script.onerror = function () {
if (onerror) onerror();
}
// 插入head
head.appendChild(script);
}
4、demo
demo地址:http://demo.qianduanblog.com/2858/1.html
5、参考资料
http://www.whatwg.org/specs/web-apps/current-work/multipage/origin-0.html