超詳細並且帶 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.blur
window.close
window.focus
window.postMessage
window.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
對象。