大家好,我是IT修真院鄭州分院第一期的學(xué)員胡嘉杰,一枚正直純潔善良的WEB前端程序員。
今天給大家分享一下,修真院官網(wǎng)JS任務(wù)5,深度思考中的知識(shí)點(diǎn)——前端跨域問題有哪些常用的解決方式?
1.背景介紹
為什么需要跨域?
受瀏覽器同源策略的限制,本域的js不能操作其他域的頁面對(duì)象(比如DOM)。 但在安全限制的同時(shí)也給注入iframe或是ajax應(yīng)用上帶來了不少麻煩。 所以我們要通過一些方法使本域的js能夠操作其他域的頁面對(duì)象或者使其他域的 js能操作本域的頁面對(duì)象(iframe之間)。
這里需要明確的一點(diǎn)是: 所謂的域跟js的存放服務(wù)器沒有關(guān)系, 比如baidu.com的頁面加載了google.com的js, 那么此js的所在域是baidu.com而不是google.com。也就是說, 此時(shí)該js能操作baidu.com的頁面對(duì)象,而不能操作google.com的頁面對(duì)象。
2.知識(shí)剖析
1.跨域:指的是瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。 它是由瀏覽器的同源策略造成的, 是瀏覽器對(duì)javascript施加的安全限制。
2.同源策略:它是由Netscape提出的一個(gè)著名的安全策略。 同源是指,域名,協(xié)議,端口相同。瀏覽器執(zhí)行javascript腳本時(shí), 會(huì)檢查這個(gè)腳本屬于那個(gè)頁面,如果不是同源頁面,就不會(huì)被執(zhí)行。
3.為什么script標(biāo)簽引入的文件不受同源策略的限制?: 因?yàn)閟cript標(biāo)簽引入的文件內(nèi)容是不能夠被客戶端的js獲取到的, 不會(huì)影響到被引用文件的安全,所以沒必要使script標(biāo)簽引入的文件遵循瀏覽器的同源策略。 而通過ajax加載的文件內(nèi)容是能夠被客戶端js獲取到的,所以ajax必須遵循同源策略, 否則被引入文件的內(nèi)容會(huì)泄漏或者存在其他風(fēng)險(xiǎn)。
3.常見問題
有哪些常用的跨域方式?
4.解決方案
4.1使用iFrame訪問另一個(gè)域。 然后再從另一個(gè)頁面讀取iFrame的內(nèi)容。jquery等有一些封裝。據(jù)說Firefox等可能不支持讀取另一個(gè)iFrame的內(nèi)容。
a)document.domain + iframe (domain 屬性可返回下載當(dāng)前文檔的服務(wù)器域名,只有在主域相同的時(shí)候才能使用該方法)
b)location.hash + iframe(原理是利用location.hash來進(jìn)行傳值):假設(shè)域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html傳遞信息。
1) cs1.html首先創(chuàng)建自動(dòng)創(chuàng)建一個(gè)隱藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html頁面
2) cs2.html響應(yīng)請(qǐng)求后再將通過修改cs1.html的hash值來傳遞數(shù)據(jù)
3) 同時(shí)在cs1.html上加一個(gè)定時(shí)器,隔一段時(shí)間來判斷l(xiāng)ocation.hash的值有沒有變化,一旦有變化則獲取獲取hash值
注:由于兩個(gè)頁面不在同一個(gè)域下IE、Chrome不允許修改parent.location.hash的值,所以要借助于a.com域名下的一個(gè)代理iframe
c)window.name + iframe(window.name 的好處在于:name 值在不同的頁面(甚至不同域名)加載后依舊存在,并且可以支持非常長(zhǎng)的 name 值【2MB】)
4.2CORS(背后的思想,就是使用自定義的HTTP頭部讓瀏覽器與服務(wù)器進(jìn)行溝通, 從而決定請(qǐng)求或響應(yīng)是應(yīng)該成功,還是應(yīng)該失敗。)
4.3跨域 XMLHttpRequest 請(qǐng)求。
4.4通過JSONP跨域: 通過script標(biāo)簽引入一個(gè)js或者是一個(gè)其他后綴形式(如PHP,jsp等) 的文件,此文件返回一個(gè)js函數(shù)的調(diào)用,如返回JSONP_getUsers(["paco","john","lili"]), 也就是說此文件返回的結(jié)果調(diào)用了JSONP_getUsers函數(shù),并且把["paco","john","lili"]傳進(jìn)去, 這個(gè)["paco","john","lili"]是一個(gè)用戶列表。那么如果此時(shí)我們的頁面中有一個(gè)JSONP_getUsers函數(shù), 那么JSONP_getUsers就被調(diào)用到,并且傳入了用戶列表。此時(shí)就實(shí)現(xiàn)了在本域獲取其他域數(shù)據(jù)的功能, 也就是跨域。
4.5 動(dòng)態(tài)創(chuàng)建script: 這種方法其實(shí)是JSONP跨域的簡(jiǎn)化版,JSONP只是在此基礎(chǔ)上加入了回調(diào)函數(shù)。
4.6服務(wù)器代理。
5.編碼實(shí)戰(zhàn)
5.1使用iFrame跨域
a)document.domain + iframe :
//在www.a.com/a.html中:
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument ||ifr.contentWindow.document;
//在這里操作doc,也就是b.html
ifr.onload = null;
};
//在www.script.a.com/b.html中:
document.domain = 'a.com';
b)location.hash + iframe:
//b.html頁面的關(guān)鍵代碼如下:
try {
parent.location.hash = 'data';
} catch (e) {
// ie、chrome的安全機(jī)制無法修改parent.location.hash,
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = "http://www.baidu.com/proxy.html#data";
document.body.appendChild(ifrproxy);
}
//proxy.html頁面的關(guān)鍵代碼如下:
//因?yàn)閜arent.parent(即baidu.com/a.html)
和baidu.com/proxy.html屬于同一個(gè)域,
所以可以改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
c)window.name + iframe:
//1) 創(chuàng)建a.com/cs1.html
//2) 創(chuàng)建a.com/proxy.html,并加入如下代碼
function proxy(url, func) {
var isFirst = true;
ifr = document.createElement('iframe');
loadFunc = function () {
if (isFirst) {
ifr.contentWindow.location = 'http://a.com/cs1.html';
isFirst = false;
} else {
func(ifr.contentWindow.name);
ifr.contentWindow.close();
document.body.removeChild(ifr);
ifr.src = '';
ifr = null;
}
};
ifr.src = url;
ifr.style.display = 'none';
if (ifr.attachEvent) ifr.attachEvent('onload', loadFunc);
else ifr.onload = loadFunc;
document.body.appendChild(iframe);
}
proxy('http://www.baidu.com/', function (data) {
console.log(data);
});
//3) 在b.com/cs1.html中包含:
window.name = '要傳送的內(nèi)容';
5.3跨域 XMLHttpRequest 請(qǐng)求:
var xhr = new XMLHttpRequest();//建立xhr對(duì)象
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {//等待回調(diào)
alert(xhr.statusText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
xhr.open("get", "example.php", false);
//請(qǐng)求的類型、請(qǐng)求的URL和是否異步發(fā)送
xhr.send(data);//參數(shù)
}
5.4通過JSONP跨域:
function handleResponse(response){
console.log('The responsed data is: '+response.data);
}
var script = document.createElement('script');
script.src = 'http://www.baidu.com/json/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
/*handleResonse({"data": "zhe"})*/
//原理如下:
//當(dāng)我們通過script標(biāo)簽請(qǐng)求時(shí)
//后臺(tái)就會(huì)根據(jù)相應(yīng)的參數(shù)(json,handleResponse)
//來生成相應(yīng)的json數(shù)據(jù)(handleResponse({"data": "zhe"}))
//最后這個(gè)返回的json數(shù)據(jù)(代碼)就會(huì)被放在當(dāng)前js文件中被執(zhí)行
//至此跨域通信完成
6.擴(kuò)展思考
怎么選擇跨域的方法?
跨域的方法很多,不同的應(yīng)用場(chǎng)景我們都可以找到一個(gè)最合適的解決方案。 比如單向的數(shù)據(jù)請(qǐng)求,我們應(yīng)該優(yōu)先選擇JSONP或者window.name, 雙向通信優(yōu)先采取location.hash, 在未與數(shù)據(jù)提供方達(dá)成通信協(xié)議的情況下我們也可以用server proxy的方式來抓取數(shù)據(jù)。
7.參考文獻(xiàn)
參考一: 前端解決跨域問題的8種方案(最新最全)
參考二:解決前端跨域請(qǐng)求的幾種方式
參考三:深入理解前端跨域方法和原理
8.更多討論
怎么用NGINX跨域?
------------------------------------------------------------------------------------------------------------------------
技能樹.IT修真院
“我們相信人人都可以成為一個(gè)工程師,現(xiàn)在開始,找個(gè)師兄,帶你入門,掌控自己學(xué)習(xí)的節(jié)奏,學(xué)習(xí)的路上不再迷?!?。
這里是技能樹.IT修真院,成千上萬的師兄在這里找到了自己的學(xué)習(xí)路線,學(xué)習(xí)透明化,成長(zhǎng)可見化,師兄1對(duì)1免費(fèi)指導(dǎo)。快來與我一起學(xué)習(xí)吧~
我的邀請(qǐng)碼:64290793,或者你可以直接點(diǎn)擊此鏈接:http://www.jnshu.com/login/1/64290793