超詳細並且帶 Demo 的 JavaScript 跨域指南來了!
本文基於你瞭解 JavaScript 的同源策略,並且瞭解使用跨域跨域的理由。
1. JSONP
首先要介紹的跨域方法必然是 JSONP。
現在你想要獲取其他網站上的 JavaScript 指令碼,你非常高興的使用 XMLHttpRequest 物件來獲取。但是瀏覽器一點兒也不配合你,無情的彈出了下面的錯誤資訊:
1 |
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:
1 2 3 4 5 6 7 |
<script> function getWeather(data) { console.log(data); } </script> <script src="http://x.y.com/xx.js"> |
http://x.y.com/xx.js 檔案內容:
1 2 3 4 |
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 中輸入程式碼:
1 2 3 |
document.domain = 'baidu.com'; var otherWindow = window.open('http://zhidao.baidu.com/'); |
我們現在已經發現百度知道的網頁已經開啟了,在百度知道網頁的 console 中輸入以下程式碼:
1 |
document.domain = 'baidu.com'; |
現在回到百度文庫的網頁,我們就可以使用百度知道網頁的 window 物件來操作百度知道的網頁了。例如:
1 |
var divs = otherWindow.document.getElementsByTagName('div'); |
上面這個例子的使用方法並不常見,但是非常詳細的說明了這種方法的原理。
這種方法主要用在控制
<iframe> 的情況中。
比如我的頁面(http://one.example.com/index.html)中內嵌了一個 <iframe> :
1 |
<iframe id="iframe" src="http://two.example.com/iframe.html"></iframe> |
我們在 iframe.html 中使用 JavaScript 將 document.domain 設定好,也就是 example.com。
在 index.html 執行以下指令碼:
1 2 3 4 5 6 |
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
我們來看以下一個場景:
隨意開啟一個頁面,輸入以下程式碼:
1 2 |
dow.name = "My window's name"; location.href = "http://www.qq.com/"; |
再檢測 window.name :
1 |
window.name; // My window's name |
可以看到,如果在一個標籤裡面跳轉網頁的話,我們的
window.name 是不會改變的。
基於這個思想,我們可以在某個頁面設定好
window.name 的值,然後跳轉到另外一個頁面。在這個頁面中就可以獲取到我們剛剛設定的 了。
由於安全原因,瀏覽器始終會保持
window.name
是string
型別。
這個方法也可以應用到與 <iframe>
的互動上來。
我的頁面(http://one.example.com/index.html)中內嵌了一個 <iframe> :
1 |
<iframe id="iframe" src="http://omg.com/iframe.html"></iframe> |
在 iframe.html 中設定好了
window.name 為我們要傳遞的字串。
我們在 index.html 中寫了下面的程式碼:
1 2 3 4 5 6 |
var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { data = iframe.contentWindow.name; }; |
定睛一看,為毛線報錯?
細心的讀者們肯定已經發現了,兩個頁面完全不同源啊!
由於 window.name 不隨著 URL 的跳轉而改變,所以我們使用一個暗黑技術來解決這個問題:
1 2 3 4 5 6 7 8 9 |
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
物件增加了一個非常有用的方法:
1 |
windowObj.postMessage(message, targetOrigin); |
- windowObj : 接受訊息的 Window 物件。
- message : 在最新的瀏覽器中可以是物件。
-
targetOrigin : 目標的源,
*
表示任意。
這個方法非常強大,無視協議,埠,域名的不同。下面是烤熟的栗子:
1 2 3 4 5 6 7 8 9 10 |
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 物件。