關(guān)于前端跨域這個(gè)老生長談的問題,解決方案也是鋪天蓋地,但大家是否真正從原理上到實(shí)踐已經(jīng)完全掌握了呢?小郭今天將總結(jié)所有的跨域解決方案,讓你在面試中收獲滿分。
本文將從跨域場景到跨域方案逐一說明,重點(diǎn)突出最常用跨域方案,讓你工作開發(fā)不用愁。
由于本文涉及篇幅較長,因此分上下集講述,力爭把最全面的跨域問題分享給大家。
概念
廣義跨域:一個(gè)域下的文檔或腳本試圖去請(qǐng)求另一個(gè)域下的資源,這被稱作為廣義上跨域。
舉例:
- 資源跳轉(zhuǎn): A鏈接、重定向、表單提交
- 資源嵌入: <link>、<script>、<img>、<frame>等dom標(biāo)簽,還有樣式中background:url()、@font-face()等文件
- 外鏈腳本請(qǐng)求: js發(fā)起的ajax請(qǐng)求、dom和js對(duì)象的跨域操作等
我們經(jīng)常討論的跨域是從狹義角度去理解,即:由瀏覽器同源策略限制的一類請(qǐng)求場景。先來解釋一下,什么是同源策略?
同源策略/SOP(Same origin policy)是一種約定,由Netscape公司1995年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。
所謂同源是指"協(xié)議+域名+端口"三者相同且必須相同,即便兩個(gè)不同的域名指向同一個(gè)ip地址,也非同源。

一張圖說明同源策略限制的的場景:

以上就是關(guān)于跨域的原理,那接下來整理一下前端都有哪些跨域解決方案,哪些是最常用的方案。
方案
- 通過jsonp跨域
- document.domain + iframe
- 跨域location.hash + iframe
- window.name + iframe跨域
- postMessage跨域跨域資源共享(CORS)
- nginx代理跨域
- nodejs中間件代理跨域
- WebSocket協(xié)議跨域
以上方案相信大家或多或少都有所了解,在這里重點(diǎn)突出常用方案。
方案一:通過jsonp跨域
通常為了減輕web服務(wù)器的負(fù)載,我們把js、css,img等靜態(tài)資源分離到另一臺(tái)獨(dú)立域名的服務(wù)器上,在html頁面中再通過相應(yīng)的標(biāo)簽從不同域名下加載靜態(tài)資源,而被瀏覽器允許,基于此原理,我們可以通過動(dòng)態(tài)創(chuàng)建script,再請(qǐng)求一個(gè)帶參網(wǎng)址實(shí)現(xiàn)跨域通信。但只能實(shí)現(xiàn)get一種請(qǐng)求。不推薦
方案二:document.domain + iframe跨域
兩個(gè)頁面都通過js強(qiáng)制設(shè)置document.domain為基礎(chǔ)主域,就實(shí)現(xiàn)了同域。因此此方案僅限主域相同,子域不同的跨域應(yīng)用場景。不推薦
方案三: location.hash + iframe跨域
a欲與b跨域相互通信,通過中間頁c來實(shí)現(xiàn)。 三個(gè)頁面,不同域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通信。
實(shí)現(xiàn)方式:A域:a.html -> B域:b.html -> A域:c.html,a與b不同域只能通過hash值單向通信,b與c也不同域也只能單向通信,但c與a同域,所以c可通過parent.parent訪問a頁面所有對(duì)象。因?yàn)閷?shí)現(xiàn)比較繁瑣,故不推薦
方案四: window.name + iframe跨域
window.name屬性的獨(dú)特之處在于name值在不同的頁面(甚至不同域名)加載后依舊存在,并且可以支持非常長的 name 值。
請(qǐng)求頁:http://www.domain1.com/a.html
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe'); // 加載跨域頁面
iframe.src = url; // onload事件會(huì)觸發(fā)2次,第1次加載跨域頁,并留存數(shù)據(jù)于
window.name iframe.onload = function() {
if (state === 1) { // 第2次onload(同域proxy頁)成功后,讀取同域window.name中數(shù)據(jù)
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) { // 第1次onload(跨域頁)成功后,切換到同域代理頁面
iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
state = 1;
}
};
document.body.appendChild(iframe); // 獲取數(shù)據(jù)以后銷毀這個(gè)iframe,釋放內(nèi)存;這也保證了安全(不被其他域frame js訪問)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};// 請(qǐng)求跨域b頁面數(shù)據(jù)
proxy('http://www.domain2.com/b.html', function(data){
alert(data);
});
代理頁:http://www.domain1.com/proxy
中間代理頁,與a.html同域,無需添加內(nèi)容
被請(qǐng)求頁:http://www.domain2.com/b.html
<script>window.name = 'This is domain2 data!';</script>
通過iframe的src屬性由外域轉(zhuǎn)向本地域,跨域數(shù)據(jù)即由iframe的window.name從外域傳遞到本地域。這個(gè)就巧妙地繞過了瀏覽器的跨域訪問限制,但同時(shí)它又是安全操作。但操作依然復(fù)雜,不推薦使用
方案五:postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是為數(shù)不多可以跨域操作的window屬性之一,他可以解決這類問題:頁面和其打開的新窗口的數(shù)據(jù)傳遞;多窗口之間消息傳遞;頁面與嵌套的iframe消息傳遞。
使用方法:postMessage(data,origin)方法接受兩個(gè)參數(shù)
data: html5規(guī)范支持任意基本類型或可復(fù)制的對(duì)象,但部分瀏覽器只支持字符串,所以傳參時(shí)最好用JSON.stringify()序列化。
origin: 協(xié)議+主機(jī)+端口號(hào),也可以設(shè)置為"*",表示可以傳遞給任意窗口,如果要指定和當(dāng)前窗口同源的話設(shè)置為"/"。
請(qǐng)求頁:http://www.domain1.com/a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
}; // 向domain2傳送跨域數(shù)據(jù)
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com'); }; // 接受domain2返回?cái)?shù)據(jù) window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
接收頁:http://www.domain2.com/b.html
<script>
// 接收domain1的數(shù)據(jù)
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16; // 處理后再發(fā)回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); } }, false);
</script>
該方法雖然直接通過window屬性解決跨域,但其適用場景有限,因此不推薦。
上集內(nèi)容到此結(jié)束。
總結(jié)一下:本篇主要帶大家弄清楚跨域的概念及跨域問題的來源,同時(shí)介紹了五種前端跨域問題的解決方案,但以上方案由于不同因素限制,所以均不推薦使用。下集內(nèi)容將講述前端最常用的幾種跨域解決方案,千萬不要錯(cuò)過。
我是小郭,想了解更多前端知識(shí)歡迎關(guān)注公眾號(hào)“一郭鮮”,小郭將帶你在前端海洋里馳騁。另外,文章也將同步更新到公眾號(hào),謝謝大家關(guān)注。
