關(guān)于前端跨域這個老生長談的問題琳猫,解決方案也是鋪天蓋地伟叛,但大家是否真正從原理上到實(shí)踐已經(jīng)完全掌握了呢?小郭今天將總結(jié)所有的跨域解決方案脐嫂,讓你在面試中收獲滿分痪伦。
本文將從跨域場景到跨域方案逐一說明侄榴,重點(diǎn)突出最常用跨域方案,讓你工作開發(fā)不用愁网沾。
由于本文涉及篇幅較長,因此分上下集講述蕊爵,力爭把最全面的跨域問題分享給大家辉哥。
概念
廣義跨域:一個域下的文檔或腳本試圖去請求另一個域下的資源,這被稱作為廣義上跨域攒射。
舉例:
- 資源跳轉(zhuǎn): A鏈接醋旦、重定向、表單提交
- 資源嵌入: <link>会放、<script>饲齐、<img>、<frame>等dom標(biāo)簽咧最,還有樣式中background:url()捂人、@font-face()等文件
- 外鏈腳本請求: js發(fā)起的ajax請求、dom和js對象的跨域操作等
我們經(jīng)常討論的跨域是從狹義角度去理解矢沿,即:由瀏覽器同源策略限制的一類請求場景滥搭。先來解釋一下,什么是同源策略捣鲸?
同源策略/SOP(Same origin policy)是一種約定瑟匆,由Netscape公司1995年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能栽惶,如果缺少了同源策略愁溜,瀏覽器很容易受到XSS、CSFR等攻擊外厂。
所謂同源是指"協(xié)議+域名+端口"三者相同且必須相同冕象,即便兩個不同的域名指向同一個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)資源分離到另一臺獨(dú)立域名的服務(wù)器上紫谷,在html頁面中再通過相應(yīng)的標(biāo)簽從不同域名下加載靜態(tài)資源齐饮,而被瀏覽器允許捐寥,基于此原理,我們可以通過動態(tài)創(chuàng)建script祖驱,再請求一個帶參網(wǎng)址實(shí)現(xiàn)跨域通信握恳。但只能實(shí)現(xiàn)get一種請求。不推薦
方案二:document.domain + iframe跨域
兩個頁面都通過js強(qiáng)制設(shè)置document.domain為基礎(chǔ)主域捺僻,就實(shí)現(xiàn)了同域乡洼。因此此方案僅限主域相同,子域不同的跨域應(yīng)用場景匕坯。不推薦
方案三: location.hash + iframe跨域
a欲與b跨域相互通信束昵,通過中間頁c來實(shí)現(xiàn)。 三個頁面葛峻,不同域之間利用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頁面所有對象榛丢。因?yàn)閷?shí)現(xiàn)比較繁瑣,故不推薦
方案四: window.name + iframe跨域
window.name屬性的獨(dú)特之處在于name值在不同的頁面(甚至不同域名)加載后依舊存在挺庞,并且可以支持非常長的 name 值晰赞。
請求頁:http://www.domain1.com/a.html
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe'); // 加載跨域頁面
iframe.src = url; // onload事件會觸發(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ù)以后銷毀這個iframe援制,釋放內(nèi)存戏挡;這也保證了安全(不被其他域frame js訪問)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};// 請求跨域b頁面數(shù)據(jù)
proxy('http://www.domain2.com/b.html', function(data){
alert(data);
});
代理頁:http://www.domain1.com/proxy
中間代理頁,與a.html同域晨仑,無需添加內(nèi)容
被請求頁:http://www.domain2.com/b.html
<script>window.name = 'This is domain2 data!';</script>
通過iframe的src屬性由外域轉(zhuǎn)向本地域褐墅,跨域數(shù)據(jù)即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制洪己,但同時它又是安全操作妥凳。但操作依然復(fù)雜,不推薦使用
方案五:postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API答捕,且是為數(shù)不多可以跨域操作的window屬性之一逝钥,他可以解決這類問題:頁面和其打開的新窗口的數(shù)據(jù)傳遞;多窗口之間消息傳遞拱镐;頁面與嵌套的iframe消息傳遞艘款。
使用方法:postMessage(data,origin)方法接受兩個參數(shù)
data: html5規(guī)范支持任意基本類型或可復(fù)制的對象持际,但部分瀏覽器只支持字符串,所以傳參時最好用JSON.stringify()序列化哗咆。
origin: 協(xié)議+主機(jī)+端口號蜘欲,也可以設(shè)置為"*",表示可以傳遞給任意窗口岳枷,如果要指定和當(dāng)前窗口同源的話設(shè)置為"/"芒填。
請求頁: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返回數(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é)一下:本篇主要帶大家弄清楚跨域的概念及跨域問題的來源盛泡,同時介紹了五種前端跨域問題的解決方案,但以上方案由于不同因素限制娱颊,所以均不推薦使用傲诵。下集內(nèi)容將講述前端最常用的幾種跨域解決方案,千萬不要錯過箱硕。
我是小郭拴竹,想了解更多前端知識歡迎關(guān)注公眾號“一郭鮮”,小郭將帶你在前端海洋里馳騁剧罩。另外栓拜,文章也將同步更新到公眾號,謝謝大家關(guān)注惠昔。