同源策略:瀏覽器出於安全考慮,會限制文檔或腳本中發起的跨域請求(但src請求不受此限)資源的加載。實際上通過抓包軟件可以發現請求和響應都會成功,但是響應數據並不會被瀏覽器加載。不同源的客戶端腳本(javascript、ActionScript)在沒明確授權的情況下,不可以使用 XMLHttpRequest 對象和Fetch發起讀寫其他web服務器的資源,主要有如下限制:
1.) Cookie、LocalStorage 和 IndexDB 無法讀取 2.) DOM 和 Js對象無法讀寫 3.) AJAX 請求不能發送
同源不是指腳本的來源,而是指腳本所嵌入的文檔來源。具體要求域名,協議和端口都相同。
比如需要加載另一個域的一張圖片資源,通過<img src='xxx'>靜態引用的方式可以成功;但是通過ajax方法會報錯:No 'Access-Control-Allow-Origin' header is present on the requested resource.
如果只是希望用post提交數據,不需要處理響應數據,可以:
a.用form的submit(跨域)提交最簡單,此時會導致當前頁面跳轉到別的域。可以將form的target設置到一個隱藏的iframe解決。
b.用ajax的post方法,但是不能監聽到響應,而且會報錯。
跨域請求數據的方法:
JSONP:最簡單,支持度高,但只能發送get請求,傳遞的參數大小有限。用js動態創建一個script標簽,其中src=’www.targetweb.com?callback=foo’,然后定義foo(data)方法,最后將script標簽append到頁面。sript標簽會自動向目標網站發送請求,服務器將數據作為參數同函數名一起返回(foo(jsonData)函數調用表達式)。實際上是返回了<script>foo(xxxx)</script>腳本文件,所以函數foo會立即調用。
//JSONP異常處理 var ele = document.createElement('script'); ele.type = "text/javascript"; ele.src = 'www.targetweb.com?callback=foo'; //異常時的回調 ele.onerror = function() { console.log('error'); }; //js成功加載(下載並執行完之后)后的回調 ele.onload = function() { console.log('loaded'); }; document.body.appendChild(ele);
相當於添加了如下script標簽:
<script> function foo(jsonpData){ //... } </script> <script src=’www.targetweb.com?callback=foo¶m1=paramSubmitByUrl’></script>
<!--后台返回的js文件實際上就是函數調用:foo(jsonpData) -->
jQuery中的跨域請求
$.ajax({ type : "get", async:true, url : "/taotao-rest/category", //跨域的請求地址 crossDomain:true, dataType : "jsonp", jsonp: "callbackName",//(不必要)定義請求參數名(相當於表單的name),供后台獲取參數使用,默認為"&callback=?"中的callback jsonpCallback:"showLists",//(不必要)自定義jsonp回調函數名稱,默認為jQuery自動生成的隨機函數名替代上述?號;在本例中后台應該返回showLists(jsonData) success : function(json){ //為什么不定義jsonpCallback也不會報錯?因為jQuery內部會自定義jsonpCallback(若無),並用responseContainer接受后台返回的數據再存入json變量中。 //window[ callbackName ] = function() { // responseContainer = arguments; //}; //最后jQuery之后會將callBackName和對應的script標簽刪除 //所以此時才可以直接用json數據 }, beforeSend: function(){ //jsonp方式時此方法不會觸發,因為不是用 XHR發送的請求 }, error: function(xhr){ //jsonp 方式時此方法不會觸發 } });
H5的WebSocket:使用ws和wss作為協議,允許跨域通信,可以相互推送信息。
iframe方式
因為jsonp只能發送get請求,所以對於跨域post提交大量數據時無能為力,此時可以用iframe跨域提交post請求。(兼容性好,兼容ie7/8/9)
<iframe>元素表示嵌套的瀏覽上下文,可以有效地將另一個HTML頁面嵌入到當前頁面中。實踐中常將form表單的target設置為iframe的name屬性,這樣submit提交后服務器返回的頁面就會在iframe中顯示,主頁面不會再刷新。iframe常用屬性:
name屬性
嵌入的iframe的名稱,該名稱可以用作<a>標簽的target屬性值,在指定的框架中打開被鏈接文檔;也可用作<form>標簽的target屬性值,規定在何處打開action URL。或<input>/<button>標簽的formtaget屬性值,規定表示提交表單后在哪里顯示接收到響應的名稱或關鍵詞,formtarget 屬性會覆蓋 <form> 元素的 target 屬性。
src
規定在 iframe 中顯示的文檔的 URL。
與iframe的交互:
在主頁面獲取iframe內的文檔對象:document.getElementById('childiframe').contentDocument.xxx
在iframe中的腳本訪問主頁面的window的屬性:window.parent.document
在主頁面訪問iframe中window里的屬性:document.getElementById('childiframe').contentWindow
注意:如果iframe和主頁面不在同一個域,則不能通過js互相訪問(window或DOM),被瀏覽器限制了。把兩個子頁面的document.domain
都指向主域則可以互相訪問。
iframe的弊端
window 的 onload 事件需要在iframe 的所有資源加載完畢才會觸發,另外會占用主頁面的可用連接數量。所以一般盡量少用iframe,要用的話最好等主頁面加載完后再設置iframe的src加載內容。
使用iframe跨域
主要是利用Window.name屬性傳遞數據。每個iframe都有window對象,其name屬性用於標記window的名字,可以存貯2M大小的數據。除非主動修改或關閉,同一個window打開任意頁面其值都不變。window.name和iframe.name是2個概念,初始化時window.name會默認取當前iframe的name的值,但是之后就無關聯。修改window.name不影響iframe.name。只有在DOM對象下可以修改iframe的name屬性。另外在設置a元素和form表單的target屬性時,window.name優先於iframe.name。
步驟:
1.使用post提交數據
使用主頁面的form跨域提交post數據,target指定到隱藏的iframe;或者在隱藏的iframe中用ajax提交post數據;
2.服務器端返回跨域下的任意空文檔,script部分需要設置window.name=data,data為服務器響應的數據。
3.將iframe的src設置為主頁面的域,否則無法訪問iframe的window。之后再從iframe的contentWindow.name中取數據。
//主頁面 var isloaded=false; //防止循環刷新iframe //chrome和ie下onload事件的觸發時機不一樣?!!! document.getElementById('iframe0').onload=function (e) { if(!isloaded){ isloaded=true; e.target.src='null.html';//設主頁面同域的任意頁面,響應數據在腳本中 }else{ //使用跨域獲取的數據 console.log(document.getElementById('iframe0').contentWindow.name); } }
CORS(Cross-origin resource sharing)跨域資源共享
如果只需要兼容IE11,則可以使用CORS。服務器實現CORS接口(在header配置Access-Control-Allow-origin屬性
),瀏覽器不會丟棄響應,就可以跨源通信了。
var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://example.com/', true); xhr.withCredentials = true;
websocket協議跨域