JS中的八種常用的跨域方式及其具體示例的總結(最新、最完整、最詳細)
JS中的八種常用的跨域方式及其具體示例的總結
這裡說的js跨域是指通過js在不同的域之間進行資料傳輸或通訊,比如用ajax向一個不同的域請求資料,或者通過js獲取頁面中不同域的框架中(iframe)的資料。
跨域問題是由於javascript語言安全限制中的同源策略造成的。
簡單來說,同源策略是指一段指令碼只能讀取來自同一來源的視窗和文件的屬性,這裡的同一來源指的是域名、協議和埠號的組合只要協議、域名、埠有任何一個不同,都被當作是不同的域。
下表給出了相對http:// www.a.com/dir/a.html同源檢測的結果:
特別注意兩點:
1、如果是協議和埠造成的跨域問題“前臺”是無能為力的;
2、在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應著兩個域或兩個域是否在同一個ip上。URL的首部:指window.location.protocol +window.location.host,也可以理解為“Domains(域名), protocols(協議) and ports(埠) must match”。
要解決跨域的問題,我們可以使用以下幾種方法:
方法一、通過jsonp跨域
JSONP包含兩部分:回撥函式和資料。
回撥函式:當響應到來時要放在當前頁面被呼叫的函式。
資料:就是傳入回撥函式中的json資料,也就是回撥函式的引數了。
/*handleResonse({"data": "zhe"})*/
//原理如下:
//當我們通過script標籤請求時
//後臺就會根據相應的引數(json,handleResponse)
//來生成相應的json資料(handleResponse({"data": "zhe"}))
//最後這個返回的json資料(程式碼)就會被放在當前js檔案中被執行
//至此跨域通訊完成
//1、使用JS動態生成script標籤,進行跨域操作
function handleResponse(response){
console.log('The responsed data is: '+response.data);
//處理獲得的Json資料
}
var script = document.createElement('script' );
script.src = 'http://www.example.com/data/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
--------------------------
//2、手動生成script標籤
function handleResponse(response){
console.log('The responsed data is: '+response.data);
//處理獲得的Json資料
}
<script src="http://www.example.com/data/?callback=handleResponse"></script>
--------------------------
//3、使用jQuery進行jsonp操作
//jquery會自動生成一個全域性函式來替換callback=?中的問號,之後獲取到資料後又會自動銷燬
//$.getJSON方法會自動判斷是否跨域,不跨域的話,就呼叫普通的ajax方法;跨域的話,則會以非同步載入js檔案的形式來呼叫jsonp的回撥函式。
<script>
$.getJson('http://www.example.com/data/?callback=?',function(jsondata){
//處理獲得的Json資料
});
</script>
jsonp雖然很簡單,但是有如下缺點:
1)安全問題(請求程式碼中可能存在安全隱患)
2)要確定jsonp請求是否失敗並不容易
方法二、通過document.domain+iframe來跨子域(只有在主域相同的時候才能使用該方法)
瀏覽器同源策略限制:
- 不能通過ajax的方法去請求不同源中的文件。
- 瀏覽器中不同域的框架之間是不能進行js的互動操作的。
所以,在不同的框架之間(父子或同輩),是能夠獲取到彼此的window物件的,但不能使用獲取到的window物件的屬性和方法(html5中的postMessage方法是一個例外),總之,你可以當做是隻能獲取到一個幾乎無用的window物件。
例如,在一個頁面 http:// www.example.com/a.html 中,有一個iframe框架它的src是http:// example.com/b.html, 很顯然,這個頁面與它裡面的iframe框架是不同域的,所以我們是無法通過在頁面中書寫js程式碼來獲取iframe中的東西的:
// http://www.example.com/a.html 頁面中
<script>
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屬性
······
}
<iframe id = "iframe" src ="http:// example.com/b.html" onload = "onLoad()"></iframe>
所以我們就要用到document.domain
1) 在頁面http:// www.a.com/dir/a.html中設定document.domain:
<iframe src = "http://script.a.com/dir/b.html" id="iframe" onload = "loLoad()"></iframe>
<script>
document.domain = "a.com";//設定成主域
function test(){
var iframe = document.getElementById("iframe");
var win = iframe.contentWindow;
//在這裡就可以操作b.html
}
</script>
2) 在http:// script.a.com/dir/b.html也需要顯示的設定document.domain
<script>
document.domain = "a.com";
</script>
注意,document.domain的設定是有限制的:
我們只能把document.domain設定成自身或更高一級的父域,且主域必須相同。
例如:a.b.c.com 中某個文件的document.domain 可以設成a.b.c.com、b.c.com 、c.com中的任意一個
方法三、使用window.name+iframe來進行跨域
window的name屬性特徵:name 值在不同的頁面(甚至不同域名)載入後依舊存在,並且可以支援非常長的 name 值(2MB),即在一個視窗(window)的生命週期內,視窗載入的所有的頁面都是共享一個window.name的,每個頁面window.name都有讀寫的許可權。
正是由於window的name屬性的特徵,所以可以使用window.name來進行跨域。
舉例:
1>在一個a.html頁面中,有如下程式碼:
<script>
window.name = "哈哈,我是頁面a設定的值喲!";
//設定window.name的值
setTimeout(function(){
window.location = 'b.html';
},3000);//3秒後把一個新頁面載入當前window
</script>
2>再在b.html中讀取window.name的值:
<script>
alert(window.name);//讀取window.name的值
</script>
3>a.html載入3秒後,跳轉到b.html頁面中,結果為:
注意:
1.window.name的值只能是字串的形式,這個字串的大小最大能允許2M左右甚至更大的一個容量,具體取決於不同的瀏覽器。
接下來使用window.name進行跨域舉例
比如:有一個example.com/a.html頁面,需要通過a.html頁面裡的js來獲取另一個位於不同域上的頁面cnblogs.com/data.html裡的資料。
1)建立cnblogs.com/data.html程式碼:
<script>
window.name = "我是data.html的資料,所有可以轉化為字串來傳遞的資料都可以在這裡使用,比如這裡可以傳遞Json資料";
</script>
2)建立example.com/a.html的程式碼:
想要即使a.html頁面不跳轉也能得到data.html裡的資料。在a.html頁面中使用一個隱藏的iframe來充當一箇中間人角色,由iframe去獲取data.html的資料,然後a.html再去得到iframe獲取到的資料。
<script>
function getData(){
//iframe載入data.html頁面會執行此函式
var ifr = document.getElementById("iframe");
ifr.onload = function(){
//這個時候iframe和a.html已經處於同一源,可以互相訪問
var data = ifr.contentWindow.name;
//獲取iframe中的window.name,也就是data.html中給它設定的資料
alert(data);
}
ifr.src = 'b.html';//這裡的b.html為隨便一個頁面,只要與a.html同源就行,目的是讓a.html能夠訪問到iframe中的東西,否則訪問不到
}
</script>
<iframe id = "iframe" src = "cnblogs.com/data.html" style = "display:none" onload = "getData()"></iframe>
方法四、使用window.postMessage方法來跨域(不常用)
window.postMessage(message,targetOrigin) 方法是html5新引進的特性,可以使用它來向其它的window物件傳送訊息,無論這個window物件是屬於同源或不同源(可實現跨域),目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支援window.postMessage方法。
message:為要傳送的訊息,型別只能為字串;
targetOrigin:用來限定接收訊息的那個window物件所在的域,如果不想限定域,可以使用萬用字元 “*”。
1)建立www.test.com/a.html頁面程式碼:
<script>
function onLoad(){
var iframe = document.getElementById("iframe");
var win = iframe.contentWindow;
win.postMessage('哈哈,我是來自頁面a.html的資訊喲!','*');//向不同域的www.script.com/b.html傳送訊息
}
</script>
<iframe id="iframe" src="www.script.com/b.html" onload="onLoad()"></iframe>
2)建立www.script.com/b.html頁面程式碼:
<script>
window.onmessage = function(e){//註冊message時間來接收訊息
e = e || event; //獲取時間物件
alert(e.data); //通過data屬性來得到傳送的訊息
}
</script>
優點:使用postMessage來跨域傳送資料還是比較直觀和方便的;
缺點: IE6、IE7不支援,所以用不用還得根據實際需要來決定。
方法五、使用跨域資源共享(CORS)來跨域
CORS:一種跨域訪問的機制,可以讓AJAX實現跨域訪問;CORS允許一個域上的網路應用向另一個域提交跨域AJAX請求。
伺服器設定Access-Control-Allow-Origin HTTP響應頭之後,瀏覽器將會允許跨域請求.
就是使用自定義的HTTP頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。
1) IE中對CORS的實現是通過xdr
var xdr = new XDomainRequest();
xdr.onload = function(){
console.log(xdr.responseText);
}
xdr.open('get', 'http://www.test.com');
......
xdr.send(null);
2) 其它瀏覽器中的實現就在xhr中
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if(xhr.readyState === 4 && xhr.status === 200){
console.log(xhr.responseText);
}
}
}
xhr.open('get', 'http://www.test.com');
......
xhr.send(null);
3) 實現跨瀏覽器的CORS
function createCORS(method, url){
var xhr = new XMLHttpRequest();
if('withCredentials' in xhr){
xhr.open(method, url, true);
}else if(typeof XDomainRequest != 'undefined'){
var xhr = new XDomainRequest();
xhr.open(method, url);
}else{
xhr = null;
}
return xhr;
}
var request = createCORS('get', 'http://www.test.com');
if(request){
request.onload = function(){
......
};
request.send();
}
方法六、使用location.hash+iframe來跨域(不常用)
假設域名test.com下的檔案a.html要和csdnblogs.com域名下的b.html傳遞資訊。
1) 建立test.com下的a.html頁面, 同時在a.html上加一個定時器,隔一段時間來判斷location.hash的值有沒有變化,一旦有變化則獲取獲取hash值,程式碼如下:
<script>
function startRequest(){
var ifr = document.createElement('iframe');
//建立一個隱藏的iframe
ifr.style.display = 'none';
ifr.src = 'http://www.csdnblogs.com/b.html#paramdo';
//傳遞的location.hash
document.body.appendChild(ifr);
}
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1):'';
if (console.log) {
console.log('Now the data is ' + data);
}
} catch (e) {};
}
setInterval(checkHash, 5000);
window.onload = startRequest;
</script>
2) b.html響應請求後再將通過修改a.html的hash值來傳遞資料,程式碼如下:
<script>
function checkHash() {
var data = '';
//模擬一個簡單的引數處理操作
switch (location.hash) {
case '#paramdo':
data = 'somedata';
break;
case '#paramset':
//do something……
break;
default:
break;
}
data && callBack('#' + data);
}
function callBack(hash) {
// ie、chrome的安全機制無法修改parent.location.hash
//所以要利用一箇中間的www.csdnblogs.com域下的代理iframe
var proxy = document.createElement('iframe');
proxy.style.display = 'none';
proxy.src = 'http://www.csdnblogs.com/c.html' + hash;
// 注意該檔案在"www.csdnblogs.com"域下
document.body.appendChild(proxy);
}
window.onload = checkHash;
</script>
3) test.com域下的c.html程式碼:
<script>
//因為parent.parent和自身屬於同一個域,所以可以改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
</script>
方法七、使用Web sockets來跨域
web sockets: 是一種瀏覽器的API,它的目標是在一個單獨的持久連線上提供全雙工、雙向通訊。(同源策略對web sockets不適用)
web sockets原理:在JS建立了web socket之後,會有一個HTTP請求傳送到瀏覽器以發起連線。取得伺服器響應後,建立的連線會使用HTTP升級從HTTP協議交換為web sockt協議。
<script>
var socket = new WebSockt('ws://www.test.com');
//http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
var data = event.data;
}
方法八、使用flash URLLoader來跨域
flash有自己的一套安全策略,伺服器可以通過crossdomain.xml檔案來宣告能被哪些域的SWF檔案訪問,SWF也可以通過API來確定自身能被哪些域的SWF載入。
例如:當跨域訪問資源時,例如從域baidu.com請求域google.com上的資料,我們可以藉助flash來發送HTTP請求。
跨域實現方式:
1.首先,修改域google.com上的crossdomain.xml(一般存放在根目錄,如果沒有需要手動建立) ,把baidu.com加入到白名單。
2. 其次,通過Flash URLLoader傳送HTTP請求
3. 最後,通過Flash API把響應結果傳遞給JavaScript。
Flash URLLoader是一種很普遍的跨域解決方案,不過需要支援iOS的話,這個方案就不可行了。
以上八種方法,可以根據專案的實際情況來進行選擇應用,個人認為window.name的方法既不復雜,也能相容到幾乎所有瀏覽器,這真是極好的一種跨域方法。