一、什麼是跨域?
要理解跨域問題,就先理解好概念。跨域問題是由於javascript語言安全限制中的同源策略造成的.
簡單來說,同源策略是指一段腳本只能讀取來自同一來源的窗口和文檔的屬性,這裏的同一來源指的是主機名、協議和端口號的組合.
URL 說明 是否允許通信 http://www.a.com/a.js http://www.a.com/b.js 同一域名下 允許 http://www.a.com/lab/a.js http://www.a.com/script/b.js 同一域名下不同文件夾 允許 http://www.a.com:8000/a.js http://www.a.com/b.js 同一域名,不同端口 不允許 http://www.a.com/a.js https://www.a.com/b.js 同一域名,不同協議 不允許 http://www.a.com/a.js http://70.32.92.74/b.js 域名和域名對應ip 不允許 http://www.a.com/a.js http://script.a.com/b.js 主域相同,子域不同 不允許 http://www.a.com/a.js http://a.com/b.js 同一域名,不同二級域名(同上) 不允許(cookie這種情況下也不允許訪問) http://www.cnblogs.com/a.js http://www.a.com/b.js 不同域名 不允許
同源策略設計之初是爲了安全,但也對正常的跨域開發造成了一定影響,不過還是有不同的解決辦法的。
二、解決辦法
跨域問題,更多的情況是出現在需要用ajax獲取數據時,那麼現在就先看個非跨域的栗子
(功能主要是從後臺獲取names列表,並展示出來)
前端部分:
<body> <div id="box"> <ul>names:</ul> </div> <script type="text/javascript" src="./js/jquery.min.js"></script> <script type="text/javascript"> function addContents(data){ var box = document.getElementById('box'), ul = box.getElementsByTagName('ul')[0], fragment = document.createDocumentFragment(), li; for(var i=0,j=data.length; i<j; i++){ li = document.createElement('li'); if(data[i].hasOwnProperty('name')){ li.appendChild(document.createTextNode(data[i].name)); fragment.appendChild(li); } } ul.appendChild(fragment); } $.ajax({ url: './cross_domain.php', //url: 'http://demoff.sinaapp.com/cross_domain.php', type: 'GET', dataType: 'json', data: {}, success: function(data){ addContents(data); }, error: function(xmlHttpRequest,textStatus,error){ console.log(xmlHttpRequest.status); console.log(textStatus); } }); </script> </body>
現在後端php是設在同域之下:
<?php // 接收數據 // $jsoncallback = $_GET["jsoncallback"]; // 構造數據 for($i=1; $i<=5; $i++){ $names[] = array("name" => "name" + $i); } // $data = $jsoncallback . "(" . json_encode($names) . ")"; $data = json_encode($names); echo $data; ?>
ok, 這樣一來數據可以正常加載,形如:
現在設置爲跨域:將ajax請求部分的url域設爲 demoff.sinaapp.com 即對換註釋部分,就會產生跨域問題
好那就進行解決吧
第一: 使用 跨域資源共享(CORS)
CORS(Cross-Origin Resource Sharing
)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與服務器應該如何溝通。CORS
背後的基本思想就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功還是失敗。
使用方法也很簡單,在php後端設置 Access-Control-Allow-Origin 頭即可,如:
<?php header("Access-Control-Allow-Origin: *"); //header("Access-Control-Allow-Origin: 我的域或ip"); // 接收數據 // $jsoncallback = $_GET["jsoncallback"]; // 構造數據 for($i=1; $i<=5; $i++){ $names[] = array("name" => "name" + $i); } // $data = $jsoncallback . "(" . json_encode($names) . ")"; $data = json_encode($names); echo $data; ?>
第二:使用jsonp
什麼是jsonp
?維基百科的定義是:JSONP(JSON with Padding).
JSONP
也叫填充式JSON,是應用JSON的一種新方法,只不過是被包含在函數調用中的JSON,例如:callback({"name","name1"});
JSONP由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數,而數據就是傳入回調函數中的JSON數據。
jsonp的原理是:
就是利用<script>標籤沒有跨域限制,來達到與第三方通訊的目的。
當需要通訊時,本站腳本創建一個<script>元素,地址指向第三方的API網址,並提供一個回調函數來接收數據(函數名可約定,或通過地址參數傳遞)。
第三方產生的響應爲json數據的包裝(故稱之爲jsonp,即json padding),形如:
callback({"name":"hax","gender":"Male"})
這樣瀏覽器會調用callback函數,並傳遞解析後json對象作爲參數。本站腳本可在callback函數裏處理所傳入的數據。
(我們知道 <link href <img src <script src 請求的數據都不受域的限制)
jsonp的使用方法:
客戶端指明使用jsonp的方式,服務器接受參數,並外包裹要返回的數據,再一併返回。
jquery的ajax簡單描述:
前端指明data:jsonp , 在標明自定義的參數名 jsonp:jsoncallback
<body> <div id="box"> <ul>names:</ul> </div> <script type="text/javascript" src="./js/jquery.min.js"></script> <script type="text/javascript"> function addContents(data){ var box = document.getElementById('box'), ul = box.getElementsByTagName('ul')[0], fragment = document.createDocumentFragment(), li; for(var i=0,j=data.length; i<j; i++){ li = document.createElement('li'); if(data[i].hasOwnProperty('name')){ li.appendChild(document.createTextNode(data[i].name)); fragment.appendChild(li); } } ul.appendChild(fragment); } $.ajax({ //url: './cross_domain.php', url: 'http://demoff.sinaapp.com/cross_domain.php', type: 'GET', dataType: 'jsonp', jsonp: 'jsoncallback', data: {}, success: function(data){ addContents(data); }, error: function(xmlHttpRequest,textStatus,error){ console.log(xmlHttpRequest.status); console.log(textStatus); } }); </script> </body>
後端服務器部分要做的就是,拿到參數,再包裹
<?php //header("Access-Control-Allow-Origin: *"); //header("Access-Control-Allow-Origin: 我的域或ip"); // 接收數據 $jsoncallback = $_GET["jsoncallback"]; // 構造數據 for($i=1; $i<=5; $i++){ $names[] = array("name" => "name" + $i); } $data = $jsoncallback . "(" . json_encode($names) . ")"; //$data = json_encode($names); echo $data; ?>
結果顯示:
你可能會奇怪這一大串是什麼,這其實是jq自動生成的一個函數名(也就是那個jsoncallback參數的值)
其實還有一種很常見的方式就是使用 $.getJson獲取,直接給出一個網址
把$.ajax部分替換成$.getJson部分
$.getJSON('http://demoff.sinaapp.com/cross_domain.php?jsoncallback=?',function(data){
addContents(data);
});
jquery
會自動生成一個全局函數來替換callback=?
中的問號,之後獲取到數據後又會自動銷燬,實際上就是起一個臨時代理函數的作用。$.getJSON
方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax
方法;跨域的話,則會以異步加載js文件的形式來調用jsonp
的回調函數。
我也可以指定那個值,因爲我們目的是要運行addContents函數,那就可以直接指定爲它。不過這時就不能使用$.getJson版的匿名函數了
直接再加個<script> 看看結果,數據返回後相應的函數就被調用執行。
<script type="text/javascript" src="http://demoff.sinaapp.com/cross_domain.php?jsoncallback=addContents"></script>
jsonp的方式很簡便,它的缺點就是:
它只支持GET請求而不支持POST等其它類型的HTTP請求;
它只支持跨域HTTP請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript
調用的問題。
第三: document.domain + iframe (iframe的使用主要是爲了ajax通信)
不同的框架之間是可以獲取window對象的,但卻無法獲取相應的屬性和方法。
比如,有一個頁面,它的地址是http://www.example.com/a.html
,
在這個頁面裏面有一個iframe
,它的src是http://example.com/b.html
,
很顯然,這個頁面與它裏面的iframe
框架是不同域的,所以我們是無法通過在頁面中書寫js代碼來獲取iframe
中的東西的:
<script type="text/javascript"> function test(){ var iframe = document.getElementById('ifame'); var win = document.contentWindow;//可以獲取到iframe裏的window對象,但該window對象的屬性和方法幾乎是不可用的 var doc = win.document;//這裏獲取不到iframe裏的document對象 var name = win.name;//這裏同樣獲取不到window對象的name屬性 } </script> <iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
這個時候,document.domain
就可以派上用場了,
我們只要把http://www.example.com/a.html
和http://example.com/b.html
這兩個頁面的document.domain都設成相同的域名就可以了。
但要注意的是,document.domain的設置是有限制的,我們只能把document.domain設置成自身或更高一級的父域,且主域必須相同。
1.在頁面 http://www.example.com/a.html
中設置document.domain
:
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe> <script type="text/javascript"> document.domain = 'example.com';//設置成主域 function test(){ alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 對象 } </script>
2.在頁面 http://example.com/b.html
中也設置document.domain
:
<script type="text/javascript"> document.domain = 'example.com';//在iframe載入這個頁面也設置document.domain,使之與主頁面的document.domain相同 </script>
上述只談到了,document.domain ,主要是爲了不同域間訪問數據操作數據。
如果想在
http://www.example.com/a.html
頁面中通過ajax直接請求下述的頁面,可以用一個隱藏的iframe來做一個代理。
http://example.com/b.html
原理就是讓這個iframe載入一個與你想要通過ajax獲取數據的目標頁面處在相同的域的頁面,所以這個iframe中的頁面是可以正常使用ajax去獲取你要的數據的,然後就是通過我們剛剛講得修改document.domain的方法,讓我們能通過js完全控制這個iframe,這樣我們就可以讓iframe去發送ajax請求,然後收到的數據我們也可以獲得了。
第四: 使用window.name + iframe
window
對象有個name
屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的所有的頁面都是共享一個window.name
的,每個頁面對window.name
都有讀寫的權限,window.name
是持久存在一個窗口載入過的所有頁面中的.
(簡單來看,window作爲瀏覽器端的全局對象,默認可不加,所以 也可以簡單地直接用name代替
但name也不是簡單地充當全局變量使用。所以要注意的是,只能使用name這個屬性,使用諸如 window.name_1之類的是不行的
我現在使用var name= 就隱式地聲明window.name了)
比如現在有兩個不同域的a.html和b.html
http://localhost:8080/demoff/a.html
<script type="text/javascript"> var name = 'myNames'; setTimeout(function(){ location = 'http://demoff.sinaapp.com/b.html'; },3000); </script>
http://demoff.sinaapp.com/b.html
<script type="text/javascript"> alert(name); </script>
3秒後跳轉過去可以看到 :
數據是存在的,但實際情況中我們也不能這樣跳來跳去,所以可以用隱藏的iframe來實現數據的獲取
舉個荔枝:
本地的爲數據提供方:http://localhost:8080/demoff/b.html
遠程的爲數據需求方:http://demoff.sinaapp.com/b.html
則本地b.html文件:
<body> <script type="text/javascript"> window.name = 'myName'; </script> </body>
遠程b.html文件
<body> <!-- 一個 iframe 作爲中間件 --> <iframe id="myIframe" src="http://localhost:8080/demoff/a.html" style="display:none;" onload="getData()"></iframe> <script type="text/javascript"> // 初始iframe加載後即執行 function getData(){ var myIframe = document.getElementById('myIframe'); // 第一次iframe加載後即可拿到window.name值,然後設置其src爲同域的文件(隨意) myIframe.src = './index.php'; // 設置好同域之後,就可以操作iframe的數據了,即可拿到該name值 myIframe.onload = function(){ var name = myIframe.contentWindow.name; alert(name); }; } </script> </body>
即可拿到數據
第五:使用 window.postMessage方法
這個東西是HTML5引入的,可以在不同的window下傳遞數據,不受域的影響。目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持該方法。
window.postMessage(message,targetOrigin)
調用postMessage方法的window對象是指要接收消息的那一個window對象,該方法的第一個參數message爲要發送的消息,類型只能爲字符串;
第二個參數targetOrigin用來限定接收消息的那個window對象所在的域,如果不想限定域,可以使用通配符 * 。
需要接收消息的window對象,可是通過監聽自身的message事件來獲取傳過來的消息,消息內容儲存在該事件對象的data屬性中。
還是舉個栗子:(假設現在是遠程提供數據,本地獲取數據)
遠程b.html
<body> <!-- 一個 iframe 作爲中間件 --> <iframe id="myIframe" src="http://localhost:8080/demoff/a.html" style="display:none;" onload="setData()"></iframe> <script type="text/javascript"> // 初始iframe加載後即執行 function setData(){ var myIframe = document.getElementById('myIframe'); var win = myIframe.contentWindow; //win.postMessage('My name is null','*'); win.postMessage('My name is null','http://localhost:8080/'); } </script> </body>
本地b.html
<body> <script type="text/javascript"> window.onmessage = function(e){ e = e || window.event; alert(e.data); }; </script> </body>
本地即可獲取到數據
第六:
除了上述常見的五種方法外,
還有flash方式的跨域,可參見
http://www.cnblogs.com/sevenyuan/archive/2009/11/19/1606237.html
http://www.2cto.com/Article/201108/100008.html
服務端也可以用一些代理的方式解決,可參見
http://blog.csdn.net/macky0668/article/details/6247803
等