js跨域 是指通过 js 在不同的域之间进行数据传输或通信,比如用 Ajax 向一个不同的域请求数据,或者通过 js 获取页面中不同域的框架中 (iframe) 的数据。只要协议、域名、端口有任何一个不同,都被当作是不同的域
浏览器允许几个元素跨域访问外部资源的,如 : <script>,<img>,<iframe>,也就是说,在 html元素中拥有 src属性的元素是可以跨域访问资源
通过 src属性,img可以引用其它站点或图床的图片,大大降低本站的图片持久
通过 src属性,script可以引用CDN的JS文件,加速了浏览器的脚本文件的下载,跨域的数据获取更加高效和方便
通过 src属性,iframe可以嵌入其它站点的页面,可以让页面的框架和可变内容分离,内容引用较为灵活,方便引用其它站点,虽然现在越来越不建议使用它
不建议使用 iframe 的原因 :
1> 搜索引擎无法查询到 iframe 中的内容,被认为是死站点,降低排名
2> iframe 创建DOM 速度慢,会阻塞页面加载
同源策略会在当 web页面 使用多个 <iframe>元素 或者打开其他浏览器窗口的时候起作用,此时脚本只能读取和所属文档来源相同的窗口和文档的属性
脚本本身的来源并不作为判断是否同源的依据,而是将脚本所属文档的来源作为判断依据
<1> 判断脚本来源 : 如果文档 A 中通过 script的 src引用一个外部脚本,这个脚本时google提供的,也是从google的主机上加载到文档A中的,那么这个脚本的所属文档是谁呢,答案是文档A
<2> 判断是否同源 : 如果两个文档在协议、主机以及载入文档的URL端口这三点中有一点不同,就认为他们不同源
同源策略如下
URL
|
说明
|
是否允许通信
|
http://www.a.com/a.js
http://www.a.com/b.js
| 同一域名下 | 允许 |
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
| 同一域名下不同文件夹 | 允许 |
http://www.a.com:8000/a.js
http://www.a.com/b.js
| 同一域名,不同端口 | 不允许 |
http://www.a.com/a.js
https://www.a.com/b.js
| 同一域名,不同协议 | 不允许 |
http://www.a.com/a.js
http://70.32.92.74/b.js
| 域名和域名对应ip | 不允许 |
http://www.a.com/a.js
http://script.a.com/b.js
| 主域相同,子域不同 | 不允许 |
http://www.a.com/a.js
http://a.com/b.js
| 同一域名,不同二级域名 (同上) | 不允许(cookie这种情况下也不允许访问) |
http://www.cnblogs.com/a.js
http://www.a.com/b.js
| 不同域名 | 不允许 |
注 : 1> 如果是协议和端口造成的跨域问题 “前端” 是无能为力的
2> 在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”
前端解决跨域问题的方法 :
1> document.domain + iframe : 默认情况下,属性domain存放的是载入文档的服务器的主机名,这一属性是可写的,只要把两个窗口包含的脚本把 domain设置为相同的值,那么这两个窗口就不再受同源策略的约束,它们就可以相互读取对方的属性
在
www.a.com/a.html 中
<body>
<iframe id="iframe" src="http://www.script.a.com/b.htmll" οnlοad="onLoad()"></iframe>
<script type="text/javascript">
document.domain = "a.com";
// 设置成主域名
function onLoad() {
var iframe = document.getElementById("iframe");
var win = iframe.contentWindow;
// 可以获得 iframe 里的 window对象,但该window对象的属性和方法几乎是不可用的
var doc = win.document;
// 获取不到 iframe 里的 document对象
var name = win.name;
// 这里同样获取不到 window对象的name属性
alert(doc + " : " + name);
var para = doc.createElement("p");
var node = doc.createTextNode("这是新段落。");
para.appendChild(node);
var element = doc.getElementById("div1");
element.appendChild(para);
}
</script>
</body>
<body>
<div id="div1"></div>
<script type="text/javascript">
document.domain = "a.com";
</script>
</body>
2> 动态创建 script : script标签不受同源策略的限制
<body>
<ul id="myList">
<li>Coffee</li>
<li>Tea</li>
<li>Water</li>
<li>Milk</li>
</ul>
<script type="text/javascript">
function loadScript(url, func) {
var list = document.getElementById("myList");
var script = document.createElement("script");
script.src = url;
script.onload = script.onreadystatechange = function () {
if (!this.readyState || this.readyState == "loaded" || this.readyState == "complete") {
func();
script.onload = script.onreadystatechange = null;
}
};
list.insertBefore(script, list.childNodes[0]);
}
window.baidu = {
sug: function (data) {
console.log(data);
}
}
loadScript("http://suggestion.baidu.com/su?wd=w", function () {
console.log("loaded")
});
// 请求的内容可以在chorme调试面板的source中看到script引入的内容
</script>
</body>
3> postMessage : HTML5中 的 XMLHttpRequest Level 2 中的 API
postMessage() 方法 允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递
postMessage(data,origin)方法接受两个参数
data : 要传递的数据,html5规范中提到该参数可以是 JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到这点,部分浏览器只能处理字符串参数,所以在传递参数的时候需要使用JSON.stringify() 方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果
origin : 字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"
在 a.com/index.html 中的代码 :
<body>
<iframe id="ifr" src="http://localhost:9001/cross_domain/index2.html"></iframe>
<script type="text/javascript">
window.onload = function () {
var ifr = document.getElementById("ifr");
var targetOrigin = "http://localhost:9001";
// 若写成 'http://b.com/c/proxy.html' 效果一样
// 若写成 'http://c.com' 就不会执行 postMessage
ifr.contentWindow.postMessage("I was there!", targetOrigin);
};
</script>
</body>
http://localhost:9001/cross_domain/index2.html 中的代码 :
<body>
<script type="text/javascript">
window.addEventListener("message", function (event) {
// 通过origin属性判断消息来源地址
if (event.origin == "http://localhost:63342") {
// event.origin 数据源 协议+主机+端口号
alert(event.data);
// event.data获取传递过来的数据,此处会弹出 "I was there!"
alert(event.source);
// 对a.com、index.html中window对象的引用但由于同源策略,这里event.source不可以访问window对象,此处会报错
}
}, false);
</script>
</body>
4> JSONP(JSON with padding) : 通过约定,访问跨域服务器上数据的方法
这种约定其实就是一个函数定义,并且具备数据参数的定义,由跨域服务器的脚本或动态生成的脚本调用并且传递数据参数
<1> 一个简单的跨域脚本调用
<body>
<script src="http://localhost:3001/javascripts/jsonpsrc1.js"></script>
</body>
<2> 跨域传递数据
<body>
<script>
function jsonpCallback(data){
alert(JSON.stringify(data,null,2));
}
</script>
<script src="http://localhost:3001/javascripts/jsonpsrc2.js"></script>
</body>
jsonpsrc2.js 中的内容
jsonpCallback({
name:'白色的海',
age:90
});
<3> 动态跨域回调函数
可以在本地服务器脚本中任意定义跨域回调函数的名称,将该函数名用过参数请求给跨域服务器,在跨域服务器后台代码上动态拼接生成回调函数的调用字符串并响应给请求方
<body>
<script type="text/javascript">
function handleResponse(response) {
console.log('The responsed data is: ' + response.data);
}
var script = document.createElement('script');
script.src = 'http://www.baidu.com/json/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
</script>
</body>
4> web sockets : 是一种浏览器的 API,它的目标是在一个单独的持久连接上提供全双工、双向通信 (同源策略对web sockets不适用)
web sockets原理 : 在js创建 web socket之后,会有一个 HTTP请求 发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用 HTTP 升级从 HTTP协议交换为web sockt协议。只有在支持 web socket协议的服务器上才能正常工作
<body>
<script type="text/javascript">
var socket = new WebSockt('ws://www.baidu.com'); // http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function (event) {
var data = event.data;
alert(data);
}
</script>
</body>