什么是跨域
說到跨域必須先解釋什么是同源策略,它是由Netscape提出的一個著名的安全策略。瀏覽器出于安全方面的考慮,只允許與本域下的接口交互。不同源的客戶端腳本在沒有明確授權的情況下,不能讀寫對方的資源?,F在所有支持JavaScript 的瀏覽器都會使用這個策略。所謂同源是指,域名,協議,端口相同。
首先我們來復習下一個完整的url組成
https://www.baidu.com:8080/aaa/1.html?id=10#name
window.location.portocol = https; //協議
window.location.host = www.baidu.com:8080; //域名
window.location.hostName = www.baidu.com;
window.location.port = 8080; //端口
window.location.search = ?id=10;
window.location.hash = #name;
還不是很熟悉的小伙伴可以參考下什么是域名?什么網站名?什么是URL?,搞懂這個才能理解好同源策略和跨域。
舉例:
同源:
http://jirengu.com/a/b.js 和 http://jirengu.com/index.php
不同源
http://jirengu.com/main.js 和 https://jirengu.com/a.php (協議不同)
http://jirengu.com/main.js 和 http://bbs.jirengu.com/a.php (域名不同,域名必須完全相同才可以)
http://jiengu.com/main.js 和 http://jirengu.com:8080/a.php (端口不同,第一個是80)
**需要注意的是: 對于當前頁面來說頁面存放的 JS 文件的域不重要,重要的是加載該 JS 頁面所在什么域 **
實際中我們經常需要到不同的域名下獲得數據,由于同源策略的存在我們無法通過AJAX等方式直接獲取數據 ,需要一些方法來實現跨域,主要的跨域方式有
- jsonp
- CORS (cross-origin-resource-shareing //iE10及以上支持)
- 降域 (具有局限性,只有是同屬于一個域名的二級域名還能夠使用這種方式)
- postMessage
下面就具體介紹下每一種跨域方式
1、jsonp
JSONP 的原理是script標簽的src屬性不受同源策略影響,網頁通過添加一個元素,相當于向服務器發送了一個get請求,服務器收到請求后,將數據放在一個指定名字的回調函數里傳回來。
舉一個簡單的例子
在目錄下建立個名稱為 1的txt文件,內容為 123,在index.html中使用srcipt標簽請求文本文件
打開控制臺查看請求,在Response中我們成功的拿到1.txt中的數據123。但是,我們雖然拿到了數據但卻沒有辦法使用~!??!這時候有人就想到了用函數調用方式,將服務器傳過來的數據以函數參數的方式調用
相當于這種寫法
這里就相當于調用了函數fn,實際中調用的這個函數名也事先約定好的,發送請求實際上就是利用DOM操作在文檔中添加一個script標簽請求數據,注意這個請求的標簽在請求完成后要刪除
一個完整的JSONP例子
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>example</title>
<style media="screen">
.newsFrame{
margin: 0 auto;
}
</style>
</head>
<body>
<div class="newsFrame">
<ul class="content">
<li>上學</li>
<li>消費</li>
<li>生活</li>
</ul>
<button class="btn">換一批</button>
</div>
<script type="text/javascript">
$('.btn').addEventListener('click',function(){
var script = document.createElement('script');
script.src='http://b.har.com:8080/getNews?callback=appendhtml';
document.head.appendChild(script);
document.head.removeChild(script);
})
function appendhtml(news){
var html = '';
for(var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>'
}
console.log(html)
$('.content').innerHTML = html
}
function $(id){
return document.querySelector(id)
}
</script>
</body>
</html>
后端
app.get('/getNews', function(req, res) {
var news = [
"看電視",
'大學在學習',
'大家在跑步',
'天氣預報',
'我要吃飯',
'看電視'
]
var data = [];
for(var i=0; i<3; i++){
var index = parseInt(Math.random()*news.length);
data.push(news[index]);
news.splice(index,1);
}
var cb = req.query.callback;
res.send( cb + '(' + JSON.stringify(data) + ')' );
});
最后,JS中還有其他三個標簽可以進行跨域請求img 、iframe、link(stylesheet)但他們都有缺陷
img
//支持跨域但是無法實現獲取服務端返回的數據
<iframe src="https://www.baidu.com"></iframe>
//支持跨域,可以接收服務端數據,但是過程復雜
<link rel="stylesheet" type="text/css" >
//會在CSS處理階段報錯
2、CORS
CORS全稱跨域資源共享(crossing-origin resourse sharing),是一種ajax跨域 請求資源的方式,支持現代瀏覽器,支持IE10以上。實現的方式很簡單,當你使用XMLHttpRequest發送請求時,瀏覽器發現該請求不符合同源策略,會給該請求加一個請求頭:Origin,后臺進行一系列處理,如果確定接受請求則在返回的結果中加入一個響應頭:Access-Control-Allow-Origin;瀏覽器判斷響應頭中是否包含Origin的值,如果有則瀏覽器會處理響應,我們就可以拿到響應數據,如果不包含瀏覽器直接駁回,這時我們無法拿到響應數據。所以,CORS的表象是讓你覺得他與同源的AJAX的請求沒啥區別,代碼完全一樣。
node的服務端
app.get('/getNews', function(req, res) {
var news = [
"看電視",
'大學在學習',
'大家在跑步',
'天氣預報',
'我要吃飯',
'看電視'
]
var data = [];
for(var i=0; i<3; i++){
var index = parseInt(Math.random()*news.length);
data.push(news[index]);
news.splice(index,1);
}
res.header("Access-Control-Allow-Origin","http://a.jrg.com:8080");//CORS
//res.header("Access-Control-Allow-Origin","*");
res.send( data );
});
深入學習可以參考阮一峰老師的博客CORS
3、降域
降域主要應用在域名相同的網站之間的跨域。
舉個例子
http://a.jrg.com:8080/a.html
http://b.jrg.com:8080/a.html
根據同源策略上面的兩個網址是不是本域,但他們的域名相同,都是jrg.com所以可以通過降域的方式跨域 。具體的實現方法分別在對應的網頁的JS中加入
document.domain = "jrg.com";
4、postMessage
postMessage作為html5新引入的API允許來自不同源的腳本采用異步方式進行有限的通信,可以實現跨文本檔、多窗口、跨域消息傳遞。
語法
1、發送
otherWindow.postMessage(message, targetOrigin, [transfer]);
- otherWindow
其他窗口的一個引用,比如iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames。 - message
將要發送到其他 window的數據。它將會被[結構化克隆算法序列化。這意味著你可以不受什么限制的將數據對象安全的傳送給目標窗口而無需自己序列化。 - targetOrigin
通過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值可以是字符串""(表示無限制)或者一個URI。在發送消息的時候,如果目標窗口的協議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那么消息就不會被發送;只有三者完全匹配,消息才會被發送。這個機制用來控制消息可以發送到哪些窗口;例如,當用postMessage傳送密碼時,這個參數就顯得尤為重要,必須保證它的值與這條包含密碼的信息的預期接受者的orign屬性完全一致,來防止密碼被惡意的第三方截獲。如果你明確的知道消息應該發送到哪個窗口,那么請始終提供一個有確切值的targetOrigin,而不是。不提供確切的目標將導致數據泄露到任何對數據感興趣的惡意站點。 -
transfer
可選是一串和message 同時傳遞的 Transferable
對象. 這些對象的所有權將被轉移給消息的接收方,而發送一方將不再保有所有權。
2、監聽
執行如下代碼, 其他window可以監聽派遣的message:
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
// For Chrome, the origin property is in the event.originalEvent
// object.
var origin = event.origin || event.originalEvent.origin;
if (origin !== "http://example.org:8080")
return;
// ...
}
message 的屬性有:
- data
從其他 window 中傳遞過來的對象。 - origin
調用 postMessage時消息發送方窗口的 origin。這個字符串由 協議、“://“、域名、“ : 端口號”拼接而成。 - source
對發送消息的窗口對象的引用; 您可以使用此來在具有不同origin的兩個窗口之間建立雙向通信。