超詳細並且帶 Demo 的 JavaScript 跨域指南來了!
本文基於你了解 JavaScript 的同源策略,並且了解使用跨域跨域的理由。
1. JSONP
首先要介紹的跨域方法必然是 JSONP。
現在你想要獲取其他網站上的 JavaScript 腳本,你非常高興的使用 XMLHttpRequest 對象來獲取。但是瀏覽器一點兒也不配合你,無情的彈出了下面的錯誤信息:
XMLHttpRequest cannot load http://x.com/main.dat. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://y.com' is therefore not allowed access.
你心里肯定會想,我難道要用后台做個爬蟲來獲取這個數據嗎?!(;°○° )
為了避免這種蛋疼的事情發生,JSONP 就派上用場了。
<script> 標簽是不受同源策略的限制的,它可以載入任意地方的 JavaScript 文件,而並不要求同源。
所以 JSONP 的理念就是,我和服務端約定好一個函數名,當我請求文件的時候,服務端返回一段 JavaScript。這段 JavaScript 調用了我們約定好的函數,並且將數據當做參數傳入。
非常巧合的一點(其實並不是),JSON 的數據格式和 JavaScript 語言里對象的格式正好相同。所以在我們約定的函數里面可以直接使用這個對象。
光說不練假把式,讓我們來看一個例子:
你需要獲取數據的頁面 index.html:
<script> function getWeather(data) { console.log(data); } </script> <script src="http://x.y.com/xx.js">
http://x.y.com/xx.js 文件內容:
getWeather({
"城市": "北京", "天氣": "大霧" });
我們可以看到,在我們定義了 getWeather(data) 這個函數后,直接載入了 xx.js。
在這個腳本中,執行了 getWeather 函數,並傳入了一個對象。然后我們在這個函數中將這個對象輸出到 console 中。
這就是整個 JSONP 的流程。
2. document.domain
使用條件:
-
有其他頁面
window對象的引用。 -
二級域名相同。
-
協議相同。
-
端口相同。
document.domain 默認的值是整個域名,所以即使兩個域名的二級域名一樣,那么他們的 document.domain 也不一樣。
使用方法就是將符合上述條件頁面的 document.domain 設置為同樣的二級域名。這樣我們就可以使用其他頁面的 window 對象引用做我們想做的任何事情了。(╯▔▽▔)╯
補充知識:
x.one.example.com 和 y.one.example.com 可以將
document.domain設置為 one.example.com,也可以設置為 example.com。
document.domain只能設置為當前域名的一個后綴,並且包括二級域名或以上(.edu.cn這種整個算頂級域名)。
我們直接操刀演示,用兩個網站 http://wenku.baidu.com/ 和 http://zhidao.baidu.com/。
這兩個網站都是 http 協議,端口都是 80, 且二級域名都是 baidu.com。
打開 http://wenku.baidu.com/,在 console 中輸入代碼:
document.domain = 'baidu.com'; var otherWindow = window.open('http://zhidao.baidu.com/');
我們現在已經發現百度知道的網頁已經打開了,在百度知道網頁的 console 中輸入以下代碼:
document.domain = 'baidu.com';
現在回到百度文庫的網頁,我們就可以使用百度知道網頁的 window 對象來操作百度知道的網頁了。例如:
var divs = otherWindow.document.getElementsByTagName('div');
上面這個例子的使用方法並不常見,但是非常詳細的說明了這種方法的原理。
這種方法主要用在控制 <iframe> 的情況中。
比如我的頁面(http://one.example.com/index.html)中內嵌了一個 <iframe>:
<iframe id="iframe" src="http://two.example.com/iframe.html"></iframe>
我們在 iframe.html 中使用 JavaScript 將 document.domain 設置好,也就是 example.com。
在 index.html 執行以下腳本:
var iframe = document.getElementById('iframe'); document.domain = 'example.com'; iframe.contentDocument; // 框架的 document 對象 iframe.contentWindow; // 框架的 window 對象
這樣,我們就可以獲得對框架的完全控制權了。
補充知識(絕對干貨):
當兩個頁面不做任何處理,但是使用了框架或者window.open()得到了某個頁面的window對象的引用,我們可以直接訪問的屬性有哪些?
方法 window.blurwindow.closewindow.focuswindow.postMessagewindow.location.replace
屬性 權限 window.closed只讀 window.frames只讀 window.length只讀 window.location.href只寫 window.opener只讀 window.parent只讀 window.self只讀 window.top只讀 window.window只讀
3. window.name
我們來看以下一個場景:
隨意打開一個頁面,輸入以下代碼:
window.name = "My window's name"; location.href = "http://www.qq.com/";
再檢測 window.name :
window.name; // My window's name
可以看到,如果在一個標簽里面跳轉網頁的話,我們的 window.name 是不會改變的。
基於這個思想,我們可以在某個頁面設置好 window.name 的值,然后跳轉到另外一個頁面。在這個頁面中就可以獲取到我們剛剛設置的 window.name 了。
由於安全原因,瀏覽器始終會保持
window.name是string類型。
這個方法也可以應用到與 <iframe> 的交互上來。
我的頁面(http://one.example.com/index.html)中內嵌了一個 <iframe>:
<iframe id="iframe" src="http://omg.com/iframe.html"></iframe>
在 iframe.html 中設置好了 window.name 為我們要傳遞的字符串。
我們在 index.html 中寫了下面的代碼:
var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { data = iframe.contentWindow.name; };
定睛一看,為毛線報錯?
細心的讀者們肯定已經發現了,兩個頁面完全不同源啊!
由於 window.name 不隨着 URL 的跳轉而改變,所以我們使用一個暗黑技術來解決這個問題:
var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { iframe.onload = function(){ data = iframe.contentWindow.name; } iframe.src = 'about:blank'; };
或者將里面的 about:blank 替換成某個同源頁面(最好是空頁面,減少加載時間)。
補充知識:
about:blank,javascript:和data:中的內容,繼承了載入他們的頁面的源。
這種方法與 document.domain 方法相比,放寬了域名后綴要相同的限制,可以從任意頁面獲取 string 類型的數據。
4. [HTML5] postMessage
在 HTML5 中, window 對象增加了一個非常有用的方法:
windowObj.postMessage(message, targetOrigin);
-
windowObj: 接受消息的 Window 對象。 -
message: 在最新的瀏覽器中可以是對象。 -
targetOrigin: 目標的源,*表示任意。
這個方法非常強大,無視協議,端口,域名的不同。下面是烤熟的栗子:
var windowObj = window; // 可以是其他的 Window 對象的引用 var data = null; addEventListener('message', function(e){ if(e.origin == 'http://jasonkid.github.io/fezone') { data = e.data; e.source.postMessage('Got it!', '*'); } });
message 事件就是用來接收 postMessage 發送過來的請求的。函數參數的屬性有以下幾個:
-
origin: 發送消息的window的源。 -
data: 數據。 -
source: 發送消息的Window對象。
