0. 結論哈哈哈
- 主域相同可以設置document.domain
- 使用JSONP動態(tài)創(chuàng)建script
- 利用iframe和location.hash
- window.name實現(xiàn)的跨域數據傳輸
- 使用HTML5 postMessage
- 使用CORS
- websocket
1. 什么是跨域
JavaScript出于安全方面的考慮,不允許跨域調用其他頁面的對象。但在安全限制的同時也給注入iframe或是ajax應用上帶來了不少麻煩。如下是一個url地址
http://www.a.com:8080
其中http是協(xié)議,www是二級域名,a.com是一級域名,8080是端口號。
以下幾種情況涉及到跨域
不同端口,協(xié)議
http://www.a.com:8000/a.js
http://www.a.com/b.js
https://www.a.com/b.js
域名和域名對應ip
http://www.a.com/a.js
http://70.32.92.74/b.js
主域相同,子域不同
http://www.a.com/a.js
http://script.a.com/b.js
特別注意兩點:
如果是協(xié)議和端口造成的跨域問題前端是無能為力的;
在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應著兩個域或兩個域是否在同一個ip上。
URL的首部 = window.location.protocol + window.location.host,也可以理解為“Domains, protocols and ports must match”。
這里簡單地總結一下一般處理跨域的辦法。
1.1 主域相同可以設置document.domain
對于主域相同而子域不同的例子,可以通過設置document.domain的辦法來解決。具體的做法是:
- 設置相同的
document.domain。在http://www.a.com/a.html和http://script.a.com/b.html兩個文件中分別加上document.domain = ‘a.com’; - 通過
a.html文件中創(chuàng)建一個iframe,去控制iframe的contentDocument,這樣兩個js文件之間就可以交互了。
代碼如下:
www.a.com上的`a.html
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
// 在這里操縱b.html
alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};
script.a.com上的b.html
document.domain = 'a.com';
這種方式適用于{www.kuqin.com, kuqin.com, script.kuqin.com, css.kuqin.com}中的任何頁面相互通信。
備注
某一頁面的domain默認等于window.location.hostname。主域名是不帶www的域名,例如a.com,主域名前面帶前綴的通常都為二級域名或多級域名,例如www.a.com其實是二級域名。domain只能設置為主域名,不可以在b.a.com中將domain設置為c.a.com。
問題
- 安全性,當一個站點被攻擊后,另一個站點會引起安全漏洞。
- 如果一個頁面中引入多個
iframe,要想能夠操作所有iframe,必須都得設置相同domain。
1.2 動態(tài)創(chuàng)建script
雖然瀏覽器默認禁止了跨域訪問,但并不禁止在頁面中引用其他域的JS文件,并可以自由執(zhí)行引入的JS文件中的function(包括操作cookie、Dom等等)。根據這一點,可以方便地通過創(chuàng)建script節(jié)點的方法來實現(xiàn)完全跨域的通信。
這里判斷script節(jié)點加載完畢還是蠻有意思的:ie只能通過script的readystatechange屬性,其它瀏覽器是script的load事件。以下是部分判斷script加載完畢的方法。
js.onload = js.onreadystatechange = function() {
if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
// callback在此處執(zhí)行
js.onload = js.onreadystatechange = null;
}
};
1.3 利用iframe和location.hash
這個辦法比較繞,但是可以解決完全跨域情況下的腳步置換問題。原理是利用location.hash來進行傳值。在urlhttp://a.com#helloword 中的‘#helloworld’就是location.hash,改變hash并不會導致頁面刷新,所以可以利用hash值來進行數據傳遞,當然數據容量是有限的。
假設域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html傳遞信息:
-
cs1.html首先自動創(chuàng)建一個隱藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html頁面,這時的hash值可以做參數傳遞用。 -
cs2.html響應請求后再將通過修改cs1.html的hash值來傳遞數據(由于兩個頁面不在同一個域下,IE、Chrome不允許修改parent.location.hash的值,所以要借助于a.com域名下的一個代理iframe;Firefox可以修改)。同時在cs1.html上加一個定時器,隔一段時間來判斷location.hash的值有沒有變化,有變化則獲取hash值。代碼如下。
先是a.com下的文件cs1.html文件:
function startRequest(){
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
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, 2000);
cnblogs.com域名下的cs2.html
// 模擬一個簡單的參數處理操作
switch(location.hash){
case '#paramdo':
callBack();
break;
case '#paramset':
//do something……
break;
}
function callBack(){
try {
parent.location.hash = 'somedata';
} catch (e) {
// ie、chrome的安全機制無法修改parent.location.hash,
// 所以要利用一個中間的cnblogs域下的代理iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; // 注意該文件在"a.com"域下
document.body.appendChild(ifrproxy);
}
}
a.com下的域名cs3.html
//因為parent.parent和自身屬于同一個域,所以可以改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
當然這樣做也存在很多缺點,諸如數據直接暴露在了url中,數據容量和類型都有限等。
1.4 window.name實現(xiàn)的跨域數據傳輸
window對象有個name屬性,該屬性有個特征:即在一個窗口(window)的生命周期內,窗口載入的所有的頁面都是共享一個window.name,每個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的所有頁面中的,并不會因新頁面的載入而進行重置。
假設我們有三個頁面。
-
a.com/app.html: 應用頁面。 -
a.com/proxy.html: 代理文件,一般是一個沒有任何內容的html文件,需要和應用頁面在同一域下。 -
b.com/data.html: 應用頁面需要獲取數據的頁面,可稱為數據頁面。
實現(xiàn)起來基本步驟如下:
1、在應用頁面(a.com/app.html) 中創(chuàng)建一個iframe,把其src指向數據頁面(b.com/data.html)。數據頁面會把數據附加到這個iframe的window.name上。
·data.html·代碼如下:
<script type="text/javascript">
window.name = 'I was there!';
// 這里是要傳輸的數據,大小一般為2M,IE和firefox下可以大至32M左右
// 數據格式可以自定義,如json、字符串
</script>
2、在應用頁面(a.com/app.html) 中監(jiān)聽iframe的onload事件,在此事件中設置這個iframe的src指向本地域的代理文件(代理文件和應用頁面在同一域下,所以可以相互通信)。
app.html部分代碼如下:
<script type="text/javascript">
var state = 0,
iframe = document.createElement('iframe'),
loadfn = function() {
if (state === 1) {
var data = iframe.contentWindow.name; // 讀取數據
alert(data); //彈出'I was there!'
} else if (state === 0) {
state = 1;
iframe.contentWindow.location = "http://a.com/proxy.html"; // 設置的代理文件
}
};
iframe.src = 'http://b.com/data.html';
if (iframe.attachEvent) {
iframe.attachEvent('onload', loadfn);
} else {
iframe.onload = loadfn;
}
document.body.appendChild(iframe);
</script>
3、獲取數據以后銷毀這個iframe,釋放內存;這也保證了安全(不被其他域frame js訪問)。
<script type="text/javascript">
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
</script>
總結:
iframe的src屬性由外域轉向本地域,跨域數據即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。
1.5 使用HTML5 postMessage
window.postMessage(message,targetOrigin)方法是html5新引進的特性,可以使用它來向其它的window對象發(fā)送消息,無論這個window對象是屬于同源或不同源,目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持window.postMessage方法。
調用postMessage方法的window對象是指要接收消息的那一個window對象,該方法的第一個參數message為要發(fā)送的消息,類型只能為字符串;第二個參數targetOrigin用來限定接收消息的那個window對象所在的域,如果不想限定域,可以使用通配符 * 。
需要接收消息的window對象,通過監(jiān)聽自身的message事件來獲取傳過來的消息,消息內容儲存在該事件對象的data屬性中。
上面所說的向其他window對象發(fā)送消息,其實就是指一個頁面有幾個框架的那種情況,因為每一個框架都有一個window對象。在討論第二種方法的時候,我們說過,不同域的框架間是可以獲取到對方的window對象的,而且也可以使用window.postMessage這個方法。
以下是演示a.com/index.html向b.com/index.html傳送數據的代碼。
a.com/index.html中的代碼:
<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://b.com'; // 若寫成'http://b.com/c/proxy.html'效果一樣
// 若寫成'http://c.com'就不會執(zhí)行postMessage了
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>
b.com/index.html中的代碼:
<script type="text/javascript">
window.addEventListener('message', function(event){
// 通過origin屬性判斷消息來源地址
if (event.origin == 'http://a.com') {
alert(event.data); // 彈出"I was there!"
alert(event.source); // 對a.com、index.html中window對象的引用
// 但由于同源策略,這里event.source不可以訪問window對象
}
}, false);
</script>
1.6 JSONP
關于JSONP詳解請移步之前的文章JSONP跨域詳解
1.7 Web Sockets 和 CORS
詳述:
AJAX實現(xiàn)跨域的幾種方法(一)
AJAX實現(xiàn)跨域的幾種方法(二)
END!跨域方式前前后后總結了四篇,終于END~~!