什么是跨域?當(dāng)兩個(gè)域具有相同的協(xié)議、相同的端口、相同的host時(shí),那么我們就可以認(rèn)為它們是相同的域。比如:http://www.example.com/a.html 和 http://www.example.com/b/c.html 就屬于相同的域,數(shù)據(jù)訪問可通過 ajax 解決。反之如果不符合上述三個(gè)條件中任何一個(gè),我們稱之為不同域。比如 http://www.example.com/a.html 和 http://example.com/b.html。由于javascript同源策略的限制,js 語言本身是不具備跨域訪問能力的,但是很多時(shí)候業(yè)務(wù)需求,加之程序員們豐富的想象力,出現(xiàn)了各種解決跨域問題的方案。這里就對比較主流的跨域方案做一個(gè)簡單的匯總。
這種跨域方案非常簡單,利用的是圖片可以跨域讀取的性質(zhì),代碼如下:
var img = new Image();
img.src = "http://example.com/data?value=123";
在 js 里執(zhí)行上述兩行代碼,就可以向 http://example.com/data 傳送數(shù)據(jù)了,而不用擔(dān)心是否和 src 里面的地址不同域。數(shù)據(jù)部分嵌入在URL中,即"value=123" 部分。
這種方案優(yōu)點(diǎn)在于非常簡單,不會帶來很多代碼的管理問題。缺點(diǎn)在于它只能單向的給服務(wù)發(fā)送數(shù)據(jù),再者由于數(shù)據(jù)嵌入在 URL 中,而URL的字符長度是受瀏覽器限制的,所以數(shù)據(jù)大小也會有限制。
Image Beacon 對于第三方的統(tǒng)計(jì)網(wǎng)站非常有用。百度統(tǒng)計(jì)、CNZZ等均有用到。
JSONP 跨域算是用的比較多的了,研究下QQ空間,就會發(fā)現(xiàn)里面很多用了JSONP技術(shù)。JSONP利用的是 script 標(biāo)簽跨域請求 js 文件的能力。實(shí)現(xiàn)方式如下:
1)首先建立一個(gè)動態(tài) script 標(biāo)簽
var script = document.createElement('script');
src = 'http://example.com/data?callback=fn';
document.body.appendChild(script);
2)注意上面的 src 部分,最后面帶有參數(shù) callback=fn。fn 必須是一個(gè)全局函數(shù),且要在在 script 標(biāo)簽 append 到 body 里面之前要聲明好的。
function fn(data) {
// 對data 進(jìn)行處理
}
3)步驟1完成之后,script 標(biāo)簽動態(tài)的生成在 body 標(biāo)簽內(nèi)。瀏覽器解析到script標(biāo)簽,給服務(wù)器(http://example.com)發(fā)送請求。服務(wù)器接受到請求后,將需要傳給客戶端的數(shù)據(jù)以json格式包在 fn 函數(shù)中,以參數(shù)的形式傳進(jìn)去,如下:
fn({value: 123}); //服務(wù)端傳給客戶端的 js
4)瀏覽器接受到服務(wù)器端傳來的 js 代碼,執(zhí)行,發(fā)現(xiàn) fn 函數(shù)已經(jīng)聲明在全局中了(步驟2),于是數(shù)據(jù)成功的傳到了 js 代碼中。
這種方式好處在于可以在客戶端和服務(wù)器端傳輸數(shù)據(jù),且不受長度限制。缺點(diǎn)在于由于是第三方服務(wù)器傳來的值,安全性不可控,所以在上述例子中,fn 函數(shù)在操作data之前,必須對data進(jìn)行校驗(yàn),以保證沒有惡意代碼注入。
有主頁面A,嵌入一個(gè)iframe B(name 屬性設(shè)為 "B"),A和B之間的通信可以分兩種情況,一種A和B同域,一種A和B不同域。
1)A和B同域
由于是同域,瀏覽器允許他們之間互相訪問,訪問方式如下:
A訪問B:winB = window.frames["B"].window; //winB 指向B中的window對象
B訪問A:winA = parent.window; //winA 指向A中的window對象
2)A和B不同域
不同域的時(shí)候,上述訪問就要出錯(cuò)了。由于安全問題,瀏覽器限制了不同域之間 js 訪問權(quán)限。既然 js 直接訪問失敗,有沒有其他方式可以實(shí)現(xiàn)主頁面和不同域的 iframe 之間的通信呢?這個(gè)時(shí)候就要發(fā)揮程序員的 hack 精神了,下面的內(nèi)容為跨域的情況,分兩種情況討論。
a)主頁面A傳數(shù)據(jù)給iframe B
在A中設(shè)置B的src屬性形式如下:
<iframe src="http://b.com/hello#data" name="B"></iframe>
data即是A要傳給B的值,此時(shí)B中可以通過 location.href 來獲取data的值了。
注意到data前面有個(gè)“#”號,它的好處在于當(dāng)通過js修改data的值時(shí)(URL改變),不會導(dǎo)致iframe 整個(gè)刷新。這樣的話,如果你需要A持續(xù)的給B傳值,就可以不斷的修改url后面data的值了,而B可以通過設(shè)置setInterval 來監(jiān)聽URL是否有改動,從而獲得改動后的data值。
b)iframe B 給主頁面A傳數(shù)據(jù)
在 iframe B 中再嵌入隱藏的一個(gè) iframe C,設(shè)置iframe C 和主頁面A同域但不同URL。假設(shè)A的url為 http://a.com,那么iframe C設(shè)置如下:
<iframe src="http://a.com/iframe_c#data" name="C"></iframe>
同樣,“#”后面的data為iframe B需要傳給A的值。由于A和C之間是同域關(guān)系,是可以通過js直接訪問的。因此只需要在C中通過JS獲取到C的URL中data部分,然后傳遞給A就行了,實(shí)現(xiàn)代碼如下:
parent.parent.fn(location.href.split('#')[1]);
注意在執(zhí)行上面這行代碼之前必須在A中聲明好全局函數(shù)fn,parent.parent 指向的即主頁面A中的window對象。
主頁面與iframe 之間的跨域通信還可以利用 window.name 這個(gè)屬性的特殊性來達(dá)到。具體就不做說明了,有興趣的同學(xué)可以在網(wǎng)上搜搜??缬虻姆绞胶芏?,關(guān)鍵是選擇一種即能滿足本身的業(yè)務(wù)需求又不影響到代碼的可維護(hù)性和擴(kuò)展性。這篇文章算是一個(gè)總結(jié),拋磚引玉。前端是個(gè)大坑,進(jìn)去容易,要想明明白白的出來,還得費(fèi)一番功夫。